use std::fmt;
use bytes::Bytes;
use celestia_grpc_macros::grpc_method;
use celestia_proto::celestia::blob::v1::query_client::QueryClient as BlobQueryClient;
use celestia_proto::celestia::core::v1::gas_estimation::gas_estimator_client::GasEstimatorClient;
use celestia_proto::celestia::core::v1::tx::tx_client::TxClient as TxStatusClient;
use celestia_proto::cosmos::auth::v1beta1::query_client::QueryClient as AuthQueryClient;
use celestia_proto::cosmos::bank::v1beta1::query_client::QueryClient as BankQueryClient;
pub use celestia_proto::cosmos::base::abci::v1beta1::GasInfo;
use celestia_proto::cosmos::base::node::v1beta1::service_client::ServiceClient as ConfigServiceClient;
use celestia_proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient as TendermintServiceClient;
use celestia_proto::cosmos::staking::v1beta1::query_client::QueryClient as StakingQueryClient;
use celestia_proto::cosmos::tx::v1beta1::service_client::ServiceClient as TxServiceClient;
use celestia_types::blob::BlobParams;
use celestia_types::block::Block;
use celestia_types::consts::appconsts;
use celestia_types::hash::Hash;
use celestia_types::state::auth::{Account, AuthParams};
use celestia_types::state::{
AbciQueryResponse, AccAddress, Address, AddressTrait, Coin, ErrorCode, PageRequest,
QueryDelegationResponse, QueryRedelegationsResponse, QueryUnbondingDelegationResponse,
TxResponse, ValAddress, BOND_DENOM,
};
use celestia_types::ExtendedHeader;
use http_body::Body;
use tonic::body::BoxBody;
use tonic::client::GrpcService;
use crate::abci_proofs::ProofChain;
use crate::{Error, Result};
mod auth;
mod bank;
mod gas_estimation;
mod node;
mod tendermint;
mod staking;
mod celestia_tx;
mod blob;
mod cosmos_tx;
pub use crate::grpc::celestia_tx::{TxStatus, TxStatusResponse};
pub use crate::grpc::cosmos_tx::{BroadcastMode, GetTxResponse};
pub use crate::grpc::gas_estimation::{GasEstimate, TxPriority};
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
pub use crate::grpc::cosmos_tx::JsBroadcastMode;
pub type StdError = Box<dyn std::error::Error + Send + Sync + 'static>;
pub struct GrpcClient<T> {
transport: T,
}
impl<T> GrpcClient<T> {
pub fn into_inner(self) -> T {
self.transport
}
}
impl<T> GrpcClient<T>
where
T: GrpcService<BoxBody> + Clone,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + Send,
{
pub fn new(transport: T) -> Self {
Self { transport }
}
#[grpc_method(AuthQueryClient::params)]
async fn get_auth_params(&self) -> Result<AuthParams>;
#[grpc_method(AuthQueryClient::account)]
async fn get_account(&self, account: &AccAddress) -> Result<Account>;
#[grpc_method(AuthQueryClient::accounts)]
async fn get_accounts(&self) -> Result<Vec<Account>>;
pub async fn get_verified_balance(
&self,
address: &Address,
header: &ExtendedHeader,
) -> Result<Coin> {
let mut prefixed_account_key = Vec::with_capacity(1 + 1 + appconsts::SIGNER_SIZE + 4);
prefixed_account_key.push(0x02); prefixed_account_key.push(address.as_bytes().len() as u8); prefixed_account_key.extend_from_slice(address.as_bytes()); prefixed_account_key.extend_from_slice(BOND_DENOM.as_bytes());
let height = 1.max(header.height().value().saturating_sub(1));
let response = self
.abci_query(&prefixed_account_key, "store/bank/key", height, true)
.await?;
if response.code != ErrorCode::Success {
return Err(Error::AbciQuery(response.code, response.log));
}
if response.value.is_empty() {
return Ok(Coin::utia(0));
}
let proof: ProofChain = response.proof_ops.unwrap_or_default().try_into()?;
proof.verify_membership(
&header.header.app_hash,
[prefixed_account_key.as_slice(), b"bank"],
&response.value,
)?;
let amount = str::from_utf8(&response.value)
.map_err(|_| Error::FailedToParseResponse)?
.parse()
.map_err(|_| Error::FailedToParseResponse)?;
Ok(Coin::utia(amount))
}
#[grpc_method(BankQueryClient::balance)]
async fn get_balance(&self, address: &Address, denom: impl Into<String>) -> Result<Coin>;
#[grpc_method(BankQueryClient::all_balances)]
async fn get_all_balances(&self, address: &Address) -> Result<Vec<Coin>>;
#[grpc_method(BankQueryClient::spendable_balances)]
async fn get_spendable_balances(&self, address: &Address) -> Result<Vec<Coin>>;
#[grpc_method(BankQueryClient::total_supply)]
async fn get_total_supply(&self) -> Result<Vec<Coin>>;
#[grpc_method(ConfigServiceClient::config)]
async fn get_min_gas_price(&self) -> Result<f64>;
#[grpc_method(TendermintServiceClient::get_latest_block)]
async fn get_latest_block(&self) -> Result<Block>;
#[grpc_method(TendermintServiceClient::get_block_by_height)]
async fn get_block_by_height(&self, height: i64) -> Result<Block>;
#[grpc_method(TendermintServiceClient::abci_query)]
async fn abci_query(
&self,
data: impl AsRef<[u8]>,
path: impl Into<String>,
height: u64,
prove: bool,
) -> Result<AbciQueryResponse>;
#[grpc_method(TxServiceClient::broadcast_tx)]
async fn broadcast_tx(&self, tx_bytes: Vec<u8>, mode: BroadcastMode) -> Result<TxResponse>;
#[grpc_method(TxServiceClient::get_tx)]
async fn get_tx(&self, hash: Hash) -> Result<GetTxResponse>;
#[grpc_method(TxServiceClient::simulate)]
async fn simulate(&self, tx_bytes: Vec<u8>) -> Result<GasInfo>;
#[grpc_method(StakingQueryClient::delegation)]
async fn query_delegation(
&self,
delegator_address: &AccAddress,
validator_address: &ValAddress,
) -> Result<QueryDelegationResponse>;
#[grpc_method(StakingQueryClient::unbonding_delegation)]
async fn query_unbonding(
&self,
delegator_address: &AccAddress,
validator_address: &ValAddress,
) -> Result<QueryUnbondingDelegationResponse>;
#[grpc_method(StakingQueryClient::redelegations)]
async fn query_redelegations(
&self,
delegator_address: &AccAddress,
src_validator_address: &ValAddress,
dest_validator_address: &ValAddress,
pagination: Option<PageRequest>,
) -> Result<QueryRedelegationsResponse>;
#[grpc_method(BlobQueryClient::params)]
async fn get_blob_params(&self) -> Result<BlobParams>;
#[grpc_method(TxStatusClient::tx_status)]
async fn tx_status(&self, hash: Hash) -> Result<TxStatusResponse>;
#[grpc_method(GasEstimatorClient::estimate_gas_price)]
async fn estimate_gas_price(&self, priority: TxPriority) -> Result<f64>;
#[grpc_method(GasEstimatorClient::estimate_gas_price_and_usage)]
async fn estimate_gas_price_and_usage(
&self,
priority: TxPriority,
tx_bytes: Vec<u8>,
) -> Result<GasEstimate>;
}
#[cfg(not(target_arch = "wasm32"))]
impl GrpcClient<tonic::transport::Channel> {
pub fn with_url(url: impl Into<String>) -> Result<Self, tonic::transport::Error> {
let channel = tonic::transport::Endpoint::from_shared(url.into())?.connect_lazy();
Ok(Self { transport: channel })
}
}
#[cfg(target_arch = "wasm32")]
impl GrpcClient<tonic_web_wasm_client::Client> {
pub fn with_grpcweb_url(url: impl Into<String>) -> Self {
Self {
transport: tonic_web_wasm_client::Client::new(url.into()),
}
}
}
impl<T> fmt::Debug for GrpcClient<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("GrpcClient { .. }")
}
}
pub(crate) trait FromGrpcResponse<T> {
fn try_from_response(self) -> Result<T>;
}
pub(crate) trait IntoGrpcParam<T> {
fn into_parameter(self) -> T;
}
macro_rules! make_empty_params {
($request_type:ident) => {
impl crate::grpc::IntoGrpcParam<$request_type> for () {
fn into_parameter(self) -> $request_type {
$request_type {}
}
}
};
}
pub(crate) use make_empty_params;