cosm_tome/modules/tx/
api.rs

1use cosmrs::proto::cosmos::tx::v1beta1::TxRaw;
2use cosmrs::tx::Body;
3use cosmrs::tx::SignerInfo;
4use serde::Serialize;
5
6use crate::chain::coin::{Coin, Denom};
7use crate::chain::error::ChainError;
8use crate::chain::msg::Msg;
9use crate::chain::response::AsyncChainTxResponse;
10use crate::modules::auth::model::{Account, Address};
11use crate::{
12    chain::{fee::Fee, request::TxOptions, response::ChainTxResponse, Any},
13    clients::client::{CosmTome, CosmosClient},
14    signing_key::key::SigningKey,
15};
16
17use super::error::TxError;
18use super::model::{BroadcastMode, RawTx};
19
20// TODO: Query endpoints
21// * tx_query_get_tx()
22// * tx_query_get_txs_event()
23// * tx_query_get_block_with_txs()
24
25impl<T: CosmosClient> CosmTome<T> {
26    pub async fn tx_sign(
27        &self,
28        msgs: Vec<impl Msg + Serialize>,
29        sender_addr: Option<Address>,
30        key: &SigningKey,
31        tx_options: &TxOptions,
32    ) -> Result<RawTx, TxError> {
33        let sender_addr = if let Some(sender_addr) = sender_addr {
34            sender_addr
35        } else {
36            key.to_addr(&self.cfg.prefix).await?
37        };
38
39        let timeout_height = tx_options.timeout_height.unwrap_or_default();
40        let account = self.auth_query_account(sender_addr).await?.account;
41
42        // even if the user is supplying their own `Fee`, we will simulate the tx to ensure its valid
43        let sim_fee = self
44            .tx_simulate(
45                msgs.iter()
46                    .map(|m| m.to_any())
47                    .collect::<Result<Vec<_>, _>>()
48                    .map_err(|e| ChainError::ProtoEncoding {
49                        message: e.to_string(),
50                    })?,
51                &account,
52            )
53            .await?;
54
55        let fee = if let Some(fee) = &tx_options.fee {
56            fee.clone()
57        } else {
58            sim_fee
59        };
60
61        let raw = key
62            .sign(
63                msgs,
64                timeout_height,
65                &tx_options.memo,
66                account,
67                fee,
68                &self.cfg,
69            )
70            .await?;
71        Ok(raw)
72    }
73
74    // Sends tx with an empty public_key / signature, like they do in the cosmos-sdk:
75    // https://github.com/cosmos/cosmos-sdk/blob/main/client/tx/tx.go#L133
76    pub async fn tx_simulate<I>(&self, msgs: I, account: &Account) -> Result<Fee, TxError>
77    where
78        I: IntoIterator<Item = Any>,
79    {
80        let tx = Body::new(msgs, "cosm-client memo", 0u16);
81
82        let denom: Denom = self.cfg.denom.parse()?;
83
84        let fee = Fee::new(
85            Coin {
86                denom: denom.clone(),
87                amount: 0u128,
88            },
89            0u64,
90            None,
91            None,
92        );
93
94        let auth_info =
95            SignerInfo::single_direct(None, account.sequence).auth_info(fee.try_into()?);
96
97        let tx_raw = TxRaw {
98            body_bytes: tx.into_bytes().map_err(ChainError::proto_encoding)?,
99            auth_info_bytes: auth_info.into_bytes().map_err(ChainError::proto_encoding)?,
100            signatures: vec![vec![]],
101        };
102
103        let gas_info = self.client.simulate_tx(&tx_raw.into()).await?;
104
105        // TODO: clean up this gas conversion code to be clearer
106        let gas_limit = (gas_info.gas_used.value() as f64 * self.cfg.gas_adjustment).ceil();
107        let amount = Coin {
108            denom,
109            amount: ((gas_limit * self.cfg.gas_price).ceil() as u64).into(),
110        };
111
112        let fee = Fee::new(amount, gas_limit as u64, None, None);
113
114        Ok(fee)
115    }
116
117    /// Non-blocking broadcast that will not wait for the tx to be committed in the next block.
118    pub async fn tx_broadcast(
119        &self,
120        tx: &RawTx,
121        mode: BroadcastMode,
122    ) -> Result<AsyncChainTxResponse, TxError> {
123        Ok(self.client.broadcast_tx(tx, mode).await?)
124    }
125
126    /// Blocking broadcast that will wait for the tx to be commited in the next block.
127    pub async fn tx_broadcast_block(&self, tx: &RawTx) -> Result<ChainTxResponse, TxError> {
128        Ok(self.client.broadcast_tx_block(tx).await?)
129    }
130}