1pub mod any_helper;
2pub mod auth;
3pub mod authz;
4pub mod bank;
5pub mod distribution;
6pub mod evidence;
7pub mod feegrant;
8pub mod gov;
9pub mod mint;
10pub mod params;
11pub mod slashing;
12pub mod staking;
13pub mod tx;
14pub mod upgrade;
15pub mod wasm;
16
17use crate::client::any_helper::{any_to_cosmos, CosmosType};
18use crate::client::tx::Response;
19use cosmos_sdk_proto::cosmos::bank::v1beta1::MsgSend;
20use cosmos_sdk_proto::cosmos::base::v1beta1::Coin;
21use cosmos_sdk_proto::cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward;
22use cosmos_sdk_proto::cosmos::staking::v1beta1::{MsgDelegate, MsgUndelegate};
23use cosmos_sdk_proto::cosmos::tx::v1beta1::{BroadcastMode, GetTxResponse};
24use cosmos_sdk_proto::ibc::applications::transfer::v1::MsgTransfer;
25use cosmos_sdk_proto::ibc::core::client::v1::Height;
26use cosmos_sdk_proto::traits::MessageExt;
27use cosmrs::tendermint::chain;
28use cosmrs::tx::{Fee, SignDoc, SignerInfo};
29use std::ops::{DivAssign, MulAssign};
30use std::rc::Rc;
31use std::str::FromStr;
32use std::thread::sleep;
33use std::time::Duration;
34use tendermint_rpc::{Client, HttpClient};
35
36use crate::error::CosmosClient;
37use crate::error::CosmosClient::{AccountDoesNotExistOnChain, NoSignerAttached};
38use crate::signer::Signer;
39use crate::tx::Cosmos;
40
41pub struct Rpc {
42 chain_id: String,
43 signer: Option<Signer>,
44 account_id: Option<u64>,
45 sequence_id: Option<u64>,
46 pub bank: bank::Module,
47 pub auth: auth::Module,
48 pub authz: authz::Module,
49 pub distribution: distribution::Module,
50 pub evidence: evidence::Module,
51 pub feegrant: feegrant::Module,
52 pub gov: gov::Module,
53 pub mint: mint::Module,
54 pub params: params::Module,
55 pub slashing: slashing::Module,
56 pub staking: staking::Module,
57 pub tx: tx::Module,
58 pub upgrade: upgrade::Module,
59 pub wasm: wasm::Module,
60}
61
62impl Rpc {
63 pub async fn new(url: &str) -> Result<Self, CosmosClient> {
68 let rpc = Rc::new(HttpClient::new(url)?);
69
70 Ok(Rpc {
71 chain_id: rpc.status().await?.node_info.network.to_string(),
72 signer: None,
73 account_id: None,
74 sequence_id: None,
75 auth: auth::Module::new(rpc.clone()),
76 authz: authz::Module::new(rpc.clone()),
77 bank: bank::Module::new(rpc.clone()),
78 distribution: distribution::Module::new(rpc.clone()),
79 evidence: evidence::Module::new(rpc.clone()),
80 feegrant: feegrant::Module::new(rpc.clone()),
81 gov: gov::Module::new(rpc.clone()),
82 mint: mint::Module::new(rpc.clone()),
83 params: params::Module::new(rpc.clone()),
84 slashing: slashing::Module::new(rpc.clone()),
85 staking: staking::Module::new(rpc.clone()),
86 tx: tx::Module::new(rpc.clone()),
87 upgrade: upgrade::Module::new(rpc.clone()),
88 wasm: wasm::Module::new(rpc),
89 })
90 }
91
92 pub async fn attach_signer(&mut self, signer: Signer) -> Result<(), CosmosClient> {
97 self.signer = Some(signer);
98 self.update_sequence_id().await?;
99 Ok(())
100 }
101
102 pub async fn update_sequence_id(&mut self) -> Result<(), CosmosClient> {
108 let signer = self.signer()?;
109
110 let account = self
111 .auth
112 .account(signer.public_address.to_string().as_str())
113 .await?;
114
115 if let Some(account) = account.account {
116 match any_to_cosmos(&account)? {
117 CosmosType::BaseAccount(account) => {
118 self.sequence_id = Some(account.sequence);
119 self.account_id = Some(account.account_number);
120 return Ok(());
121 }
122 CosmosType::ContinuousVestingAccount(account) => {
123 let account = account
124 .base_vesting_account
125 .ok_or(CosmosClient::NoVestingBaseAccount)?
126 .base_account
127 .ok_or(CosmosClient::NoVestingBaseAccount)?;
128 self.sequence_id = Some(account.sequence);
129 self.account_id = Some(account.account_number);
130 return Ok(());
131 }
132 _ => {}
133 }
134 }
135
136 Err(AccountDoesNotExistOnChain {
137 address: signer.public_address.to_string(),
138 })
139 }
140
141 pub async fn sign(&mut self, tx: Cosmos) -> Result<Vec<u8>, CosmosClient> {
148 let account_id = self.account_id.ok_or(AccountDoesNotExistOnChain {
149 address: self.signer()?.public_address.to_string(),
150 })?;
151 let sequence_id = self.sequence_id.ok_or(AccountDoesNotExistOnChain {
152 address: self.signer()?.public_address.to_string(),
153 })?;
154 self.sequence_id = Some(self.sequence_id.unwrap_or_default() + 1u64);
155
156 let signer = self.signer()?;
157
158 let tx_body = tx.finish();
159 let auth_info = SignerInfo::single_direct(Some(signer.public_key), sequence_id).auth_info(
160 Fee::from_amount_and_gas(
161 cosmrs::Coin {
162 amount: signer.gas_price,
163 denom: signer.denom.parse()?,
164 },
165 100u64,
166 ),
167 );
168
169 let sign_doc = SignDoc::new(
170 &tx_body,
171 &auth_info,
172 &chain::Id::from_str(self.chain_id.as_str())?,
173 account_id,
174 )?;
175 let tx_raw = sign_doc.sign(&signer.private_key)?;
176 let tx = self.tx.simulate(tx_raw.to_bytes()?).await?;
177
178 if tx.gas_info.is_none() {
179 return Err(CosmosClient::CannotSimulateTxGasFee);
180 }
181
182 let mut gas_info = tx.gas_info.unwrap_or_default().gas_used;
183 gas_info.mul_assign(100u64 + u64::from(signer.gas_adjustment_percent));
184 gas_info.div_assign(100);
185
186 let auth_info = SignerInfo::single_direct(Some(signer.public_key), sequence_id).auth_info(
187 Fee::from_amount_and_gas(
188 cosmrs::Coin {
189 amount: signer.gas_price,
190 denom: signer.denom.parse()?,
191 },
192 gas_info,
193 ),
194 );
195
196 let sign_doc = SignDoc::new(
197 &tx_body,
198 &auth_info,
199 &chain::Id::from_str(self.chain_id.as_str())?,
200 account_id,
201 )?;
202
203 Ok(sign_doc.sign(&signer.private_key)?.to_bytes()?)
204 }
205
206 pub async fn broadcast(
211 &mut self,
212 payload: Vec<u8>,
213 mode: BroadcastMode,
214 ) -> Result<Response, CosmosClient> {
215 self.tx.broadcast(payload, mode).await
216 }
217
218 pub async fn sign_and_broadcast(
223 &mut self,
224 tx: Cosmos,
225 mode: BroadcastMode,
226 ) -> Result<Response, CosmosClient> {
227 let payload = self.sign(tx).await?;
228
229 self.tx.broadcast(payload, mode).await
230 }
231
232 fn signer(&self) -> Result<&Signer, CosmosClient> {
233 self.signer.as_ref().ok_or(NoSignerAttached)
234 }
235
236 async fn poll_for_tx(&self, tx: Response) -> Result<GetTxResponse, CosmosClient> {
237 let hash = match tx {
238 Response::Sync(tx) => tx.hash,
239 Response::Async(tx) => tx.hash,
240 Response::Commit(tx) => tx.hash,
241 };
242
243 for _ in 0..60 {
244 let tx = self.tx.get_tx(hash.to_string().as_str()).await;
245
246 if tx.is_ok() {
247 return tx;
248 }
249 sleep(Duration::from_secs(3));
250 }
251
252 Err(CosmosClient::TXPollingTimeout)
253 }
254
255 pub async fn send(
262 &mut self,
263 to: &str,
264 coin: Vec<Coin>,
265 memo: Option<&str>,
266 ) -> Result<GetTxResponse, CosmosClient> {
267 let signer = self.signer()?;
268
269 let mut payload = Cosmos::build().add_msg(
270 MsgSend {
271 from_address: signer.public_address.to_string(),
272 to_address: to.to_string(),
273 amount: coin,
274 }
275 .to_any()?,
276 );
277 if let Some(memo) = memo {
278 payload = payload.memo(memo);
279 }
280
281 let tx = self
282 .sign_and_broadcast(payload, BroadcastMode::Sync)
283 .await?;
284 self.poll_for_tx(tx).await
285 }
286
287 pub async fn stake(
294 &mut self,
295 to: &str,
296 coin: Coin,
297 memo: Option<&str>,
298 ) -> Result<GetTxResponse, CosmosClient> {
299 let signer = self.signer()?;
300
301 let mut payload = Cosmos::build().add_msg(
302 MsgDelegate {
303 delegator_address: signer.public_address.to_string(),
304 validator_address: to.to_string(),
305 amount: Some(coin),
306 }
307 .to_any()?,
308 );
309 if let Some(memo) = memo {
310 payload = payload.memo(memo);
311 }
312
313 let tx = self
314 .sign_and_broadcast(payload, BroadcastMode::Sync)
315 .await?;
316 self.poll_for_tx(tx).await
317 }
318
319 pub async fn unstake(
326 &mut self,
327 to: &str,
328 coin: Coin,
329 memo: Option<&str>,
330 ) -> Result<GetTxResponse, CosmosClient> {
331 let signer = self.signer()?;
332
333 let mut payload = Cosmos::build().add_msg(
334 MsgUndelegate {
335 delegator_address: signer.public_address.to_string(),
336 validator_address: to.to_string(),
337 amount: Some(coin),
338 }
339 .to_any()?,
340 );
341 if let Some(memo) = memo {
342 payload = payload.memo(memo);
343 }
344
345 let tx = self
346 .sign_and_broadcast(payload, BroadcastMode::Sync)
347 .await?;
348 self.poll_for_tx(tx).await
349 }
350
351 pub async fn claim_rewards(
358 &mut self,
359 to: &str,
360 memo: Option<&str>,
361 ) -> Result<GetTxResponse, CosmosClient> {
362 let signer = self.signer()?;
363
364 let mut payload = Cosmos::build().add_msg(
365 MsgWithdrawDelegatorReward {
366 delegator_address: signer.public_address.to_string(),
367 validator_address: to.to_string(),
368 }
369 .to_any()?,
370 );
371 if let Some(memo) = memo {
372 payload = payload.memo(memo);
373 }
374
375 let tx = self
376 .sign_and_broadcast(payload, BroadcastMode::Sync)
377 .await?;
378 self.poll_for_tx(tx).await
379 }
380
381 #[allow(clippy::too_many_arguments)]
388 pub async fn ibc_send(
389 &mut self,
390 to: &str,
391 coin: Coin,
392 source_port: &str,
393 source_channel: &str,
394 timeout_height: Option<Height>,
395 timeout_timestamp: u64,
396 memo: Option<&str>,
397 ) -> Result<GetTxResponse, CosmosClient> {
398 let signer = self.signer()?;
399
400 let mut payload = Cosmos::build().add_msg(
401 MsgTransfer {
402 source_port: source_port.to_string(),
403 source_channel: source_channel.to_string(),
404 token: Some(coin),
405 sender: signer.public_address.to_string(),
406 receiver: to.to_string(),
407 timeout_height,
408 timeout_timestamp,
409 }
410 .to_any()?,
411 );
412 if let Some(memo) = memo {
413 payload = payload.memo(memo);
414 }
415
416 let tx = self
417 .sign_and_broadcast(payload, BroadcastMode::Sync)
418 .await?;
419 self.poll_for_tx(tx).await
420 }
421}