1use crate::prelude::*;
2use crate::querier::tx::AnyTxResponse;
3use crate::signing::middleware::{SigningMiddlewareMapBody, SigningMiddlewareMapResp};
4use std::sync::{
5 atomic::{AtomicBool, AtomicU64},
6 Arc,
7};
8
9use layer_climb_signer::TxSigner;
10
11pub struct TxBuilder<'a> {
12 pub querier: &'a QueryClient,
13 pub signer: &'a dyn TxSigner,
14
15 pub sender: Option<Address>,
17
18 pub tx_timeout_blocks: Option<u64>,
21 pub sequence_strategy: Option<SequenceStrategy>,
23
24 pub account_number: Option<u64>,
26
27 pub memo: Option<String>,
28
29 pub gas_coin: Option<layer_climb_proto::Coin>,
32
33 pub gas_units_or_simulate: Option<u64>,
36
37 pub gas_simulate_multiplier: Option<f32>,
40
41 pub broadcast_mode: Option<layer_climb_proto::tx::BroadcastMode>,
43
44 pub broadcast_poll: bool,
47
48 pub broadcast_poll_sleep_duration: Option<std::time::Duration>,
51
52 pub broadcast_poll_timeout_duration: Option<std::time::Duration>,
55
56 pub middleware_map_body: Option<Arc<Vec<SigningMiddlewareMapBody>>>,
58
59 pub middleware_map_resp: Option<Arc<Vec<SigningMiddlewareMapResp>>>,
61}
62
63impl<'a> TxBuilder<'a> {
64 const DEFAULT_TX_TIMEOUT_BLOCKS: u64 = 10;
65 const DEFAULT_GAS_MULTIPLIER: f32 = 1.5;
66 const DEFAULT_BROADCAST_MODE: layer_climb_proto::tx::BroadcastMode =
67 layer_climb_proto::tx::BroadcastMode::Sync;
68 const DEFAULT_BROADCAST_POLL_SLEEP_DURATION: std::time::Duration =
69 std::time::Duration::from_secs(1);
70 const DEFAULT_BROADCAST_POLL_TIMEOUT_DURATION: std::time::Duration =
71 std::time::Duration::from_secs(30);
72
73 pub fn new(querier: &'a QueryClient, signer: &'a dyn TxSigner) -> Self {
74 Self {
75 querier,
76 signer,
77 gas_coin: None,
78 sender: None,
79 memo: None,
80 tx_timeout_blocks: None,
81 sequence_strategy: None,
82 gas_units_or_simulate: None,
83 gas_simulate_multiplier: None,
84 account_number: None,
85 broadcast_mode: None,
86 broadcast_poll: true,
87 broadcast_poll_sleep_duration: None,
88 broadcast_poll_timeout_duration: None,
89 middleware_map_body: None,
90 middleware_map_resp: None,
91 }
92 }
93
94 pub fn set_tx_timeout_blocks(&mut self, tx_timeout_blocks: u64) -> &mut Self {
95 self.tx_timeout_blocks = Some(tx_timeout_blocks);
96 self
97 }
98
99 pub fn set_memo(&mut self, memo: impl Into<String>) -> &mut Self {
100 self.memo = Some(memo.into());
101 self
102 }
103
104 pub fn set_sequence_strategy(&mut self, sequence_strategy: SequenceStrategy) -> &mut Self {
105 self.sequence_strategy = Some(sequence_strategy);
106 self
107 }
108
109 pub fn set_sender(&mut self, sender: Address) -> &mut Self {
110 self.sender = Some(sender);
111 self
112 }
113
114 pub fn set_gas_coin(&mut self, gas_coin: layer_climb_proto::Coin) -> &mut Self {
115 self.gas_coin = Some(gas_coin);
116 self
117 }
118
119 pub fn set_gas_units_or_simulate(&mut self, gas_units: Option<u64>) -> &mut Self {
120 self.gas_units_or_simulate = gas_units;
121 self
122 }
123
124 pub fn set_gas_simulate_multiplier(&mut self, gas_multiplier: f32) -> &mut Self {
125 self.gas_simulate_multiplier = Some(gas_multiplier);
126 self
127 }
128
129 pub fn set_account_number(&mut self, account_number: u64) -> &mut Self {
130 self.account_number = Some(account_number);
131 self
132 }
133
134 pub fn set_broadcast_mode(
135 &mut self,
136 broadcast_mode: layer_climb_proto::tx::BroadcastMode,
137 ) -> &mut Self {
138 self.broadcast_mode = Some(broadcast_mode);
139 self
140 }
141
142 pub fn set_broadcast_poll(&mut self, broadcast_poll: bool) -> &mut Self {
143 self.broadcast_poll = broadcast_poll;
144 self
145 }
146
147 pub fn set_broadcast_poll_sleep_duration(
148 &mut self,
149 broadcast_poll_sleep_duration: std::time::Duration,
150 ) -> &mut Self {
151 self.broadcast_poll_sleep_duration = Some(broadcast_poll_sleep_duration);
152 self
153 }
154
155 pub fn set_broadcast_poll_timeout_duration(
156 &mut self,
157 broadcast_poll_timeout_duration: std::time::Duration,
158 ) -> &mut Self {
159 self.broadcast_poll_timeout_duration = Some(broadcast_poll_timeout_duration);
160 self
161 }
162
163 pub fn set_middleware_map_body(
164 &mut self,
165 middleware_map_body: Arc<Vec<SigningMiddlewareMapBody>>,
166 ) -> &mut Self {
167 self.middleware_map_body = Some(middleware_map_body);
168 self
169 }
170
171 pub fn set_middleware_map_resp(
172 &mut self,
173 middleware_map_resp: Arc<Vec<SigningMiddlewareMapResp>>,
174 ) -> &mut Self {
175 self.middleware_map_resp = Some(middleware_map_resp);
176 self
177 }
178
179 async fn query_base_account(&self) -> Result<layer_climb_proto::auth::BaseAccount> {
180 self.querier
181 .base_account(
182 self.sender
183 .as_ref()
184 .with_context(|| "must provide a sender if no sequence")?,
185 )
186 .await
187 }
188
189 pub async fn broadcast(
190 self,
191 messages: impl IntoIterator<Item = layer_climb_proto::Any>,
192 ) -> Result<layer_climb_proto::abci::TxResponse> {
193 let messages = messages.into_iter().collect();
194 let resp = self.broadcast_raw(messages).await?;
195
196 match resp {
197 AnyTxResponse::Abci(tx_response) => Ok(tx_response),
198 AnyTxResponse::Rpc(_) => Err(anyhow!(
199 "Unexpected AnyTxResponse type - did you mean to call broadcast_raw instead?"
200 )),
201 }
202 }
203
204 pub async fn simulate_gas(
205 &self,
206 signer_info: layer_climb_proto::tx::SignerInfo,
207 account_number: u64,
208 tx_body: &mut layer_climb_proto::tx::TxBody,
210 ) -> Result<layer_climb_proto::abci::GasInfo> {
211 let fee = FeeCalculation::Simulation {
212 chain_config: &self.querier.chain_config,
213 }
214 .calculate()?;
215
216 let simulate_tx_resp = self
217 .querier
218 .simulate_tx(
219 self.sign_tx(signer_info, account_number, tx_body, fee, true)
220 .await?,
221 )
222 .await?;
223
224 simulate_tx_resp
225 .gas_info
226 .context("unable to get gas from simulation")
227 }
228
229 pub async fn broadcast_raw(
233 self,
234 messages: Vec<layer_climb_proto::Any>,
235 ) -> Result<AnyTxResponse> {
236 let mut base_account: Option<layer_climb_proto::auth::BaseAccount> = None;
237
238 let sequence = match &self.sequence_strategy {
239 Some(sequence_strategy) => match sequence_strategy.kind {
240 SequenceStrategyKind::Query => {
241 base_account = Some(self.query_base_account().await?);
242 base_account.as_ref().unwrap().sequence
243 }
244 SequenceStrategyKind::QueryAndIncrement => {
245 if !sequence_strategy
246 .has_queried
247 .load(std::sync::atomic::Ordering::SeqCst)
248 {
249 base_account = Some(self.query_base_account().await?);
250 sequence_strategy
251 .has_queried
252 .store(true, std::sync::atomic::Ordering::SeqCst);
253 sequence_strategy.value.store(
254 base_account.as_ref().unwrap().sequence,
255 std::sync::atomic::Ordering::SeqCst,
256 );
257 base_account.as_ref().unwrap().sequence
258 } else {
259 sequence_strategy
260 .value
261 .load(std::sync::atomic::Ordering::SeqCst)
262 }
263 }
264 SequenceStrategyKind::SetAndIncrement(_) => sequence_strategy
265 .value
266 .load(std::sync::atomic::Ordering::SeqCst),
267 SequenceStrategyKind::Constant(n) => n,
268 },
269 None => {
270 base_account = Some(self.query_base_account().await?);
271 base_account.as_ref().unwrap().sequence
272 }
273 };
274
275 let account_number = match self.account_number {
276 Some(account_number) => account_number,
277 None => match base_account {
278 Some(base_account) => base_account.account_number,
279 None => self.query_base_account().await?.account_number,
280 },
281 };
282
283 let mut body = layer_climb_proto::tx::TxBody {
284 messages,
285 memo: self.memo.as_deref().unwrap_or("").to_string(),
286 timeout_height: 0, extension_options: Default::default(),
288 non_critical_extension_options: Default::default(),
289 };
290
291 if let Some(middleware) = self.middleware_map_body.as_ref() {
292 for middleware in middleware.iter() {
293 body = match middleware.map_body(body).await {
294 Ok(req) => req,
295 Err(e) => return Err(e),
296 }
297 }
298 }
299
300 let gas_units = match self.gas_units_or_simulate {
301 Some(gas_units) => gas_units,
302 None => {
303 let gas_multiplier = self
304 .gas_simulate_multiplier
305 .unwrap_or(Self::DEFAULT_GAS_MULTIPLIER);
306
307 let signer_info = self
308 .signer
309 .signer_info(sequence, layer_climb_proto::tx::SignMode::Unspecified)
310 .await?;
311
312 let gas_info = self
313 .simulate_gas(signer_info, account_number, &mut body)
314 .await?;
315
316 (gas_info.gas_used as f32 * gas_multiplier).ceil() as u64
317 }
318 };
319
320 let fee = match self.gas_coin.clone() {
321 Some(gas_coin) => FeeCalculation::RealCoin {
322 gas_coin,
323 gas_units,
324 }
325 .calculate()?,
326 None => FeeCalculation::RealNetwork {
327 chain_config: &self.querier.chain_config,
328 gas_units,
329 }
330 .calculate()?,
331 };
332
333 let signer_info = self
334 .signer
335 .signer_info(sequence, layer_climb_proto::tx::SignMode::Direct)
336 .await?;
337
338 let tx_bytes = self
339 .sign_tx(signer_info, account_number, &mut body, fee, false)
340 .await?;
341 let broadcast_mode = self.broadcast_mode.unwrap_or(Self::DEFAULT_BROADCAST_MODE);
342
343 let tx_response = self
344 .querier
345 .broadcast_tx_bytes(tx_bytes, broadcast_mode)
346 .await?;
347
348 if let Some(sequence) = self.sequence_strategy {
350 match sequence.kind {
351 SequenceStrategyKind::QueryAndIncrement => {
352 sequence
353 .value
354 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
355 }
356 SequenceStrategyKind::SetAndIncrement(_) => {
357 sequence
358 .value
359 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
360 }
361 _ => {}
362 }
363 }
364
365 if tx_response.code() != 0 {
366 bail!(
367 "tx failed with code: {}, codespace: {}, raw_log: {}",
368 tx_response.code(),
369 tx_response.codespace(),
370 tx_response.raw_log()
371 );
372 }
373
374 let mut tx_response = if self.broadcast_poll {
375 let sleep_duration = self
376 .broadcast_poll_sleep_duration
377 .unwrap_or(Self::DEFAULT_BROADCAST_POLL_SLEEP_DURATION);
378 let timeout_duration = self
379 .broadcast_poll_timeout_duration
380 .unwrap_or(Self::DEFAULT_BROADCAST_POLL_TIMEOUT_DURATION);
381
382 AnyTxResponse::Abci(
383 self.querier
384 .poll_until_tx_ready(tx_response.tx_hash(), sleep_duration, timeout_duration)
385 .await?
386 .tx_response,
387 )
388 } else {
389 tx_response
390 };
391
392 if tx_response.code() != 0 {
393 bail!(
394 "tx failed with code: {}, codespace: {}, raw_log: {}",
395 tx_response.code(),
396 tx_response.codespace(),
397 tx_response.raw_log()
398 );
399 }
400
401 if let Some(middleware) = self.middleware_map_resp.as_ref() {
402 for middleware in middleware.iter() {
403 tx_response = match middleware.map_resp(tx_response).await {
404 Ok(req) => req,
405 Err(e) => return Err(e),
406 }
407 }
408 }
409
410 Ok(tx_response)
411 }
412
413 async fn sign_tx(
414 &self,
415 signer_info: layer_climb_proto::tx::SignerInfo,
416 account_number: u64,
417 body: &mut layer_climb_proto::tx::TxBody,
419 fee: layer_climb_proto::tx::Fee,
420 simulate_only: bool,
421 ) -> Result<Vec<u8>> {
422 #[allow(deprecated)]
423 let auth_info = layer_climb_proto::tx::AuthInfo {
424 signer_infos: vec![signer_info],
425 fee: Some(fee),
426 tip: None,
427 };
428
429 let block_height = self.querier.block_height().await?;
430
431 let tx_timeout_blocks = self
432 .tx_timeout_blocks
433 .unwrap_or(Self::DEFAULT_TX_TIMEOUT_BLOCKS);
434
435 body.timeout_height = block_height + tx_timeout_blocks;
437
438 let sign_doc = layer_climb_proto::tx::SignDoc {
439 body_bytes: proto_into_bytes(body)?,
440 auth_info_bytes: proto_into_bytes(&auth_info)?,
441 chain_id: self.querier.chain_config.chain_id.to_string(),
442 account_number,
443 };
444
445 let signature = match simulate_only {
446 true => Vec::new(),
447 false => self.signer.sign(&sign_doc).await?,
448 };
449
450 let tx_raw = layer_climb_proto::tx::TxRaw {
451 body_bytes: sign_doc.body_bytes.clone(),
452 auth_info_bytes: sign_doc.auth_info_bytes.clone(),
453 signatures: vec![signature],
454 };
455
456 proto_into_bytes(&tx_raw)
457 }
458}
459
460#[derive(Clone, Debug)]
461pub struct SequenceStrategy {
462 pub kind: SequenceStrategyKind,
463 pub value: Arc<AtomicU64>,
464 pub has_queried: Arc<AtomicBool>,
465}
466
467impl SequenceStrategy {
468 pub fn new(kind: SequenceStrategyKind) -> Self {
469 Self {
470 value: Arc::new(AtomicU64::new(match kind {
471 SequenceStrategyKind::Query => 0, SequenceStrategyKind::QueryAndIncrement => 0, SequenceStrategyKind::SetAndIncrement(n) => n,
474 SequenceStrategyKind::Constant(n) => n,
475 })),
476 kind,
477 has_queried: Arc::new(AtomicBool::new(false)),
478 }
479 }
480}
481
482#[derive(Clone, Debug)]
483pub enum SequenceStrategyKind {
484 Query,
486 QueryAndIncrement,
488 SetAndIncrement(u64),
490 Constant(u64),
492}
493
494pub enum FeeCalculation<'a> {
495 Simulation {
496 chain_config: &'a ChainConfig,
497 },
498 RealNetwork {
499 chain_config: &'a ChainConfig,
500 gas_units: u64,
501 },
502 RealCoin {
503 gas_coin: layer_climb_proto::Coin,
504 gas_units: u64,
505 },
506}
507
508impl FeeCalculation<'_> {
509 pub fn calculate(&self) -> Result<layer_climb_proto::tx::Fee> {
510 let (gas_coin, gas_limit) = match self {
511 Self::Simulation { chain_config } => (new_coin(0, &chain_config.gas_denom), 0),
512 Self::RealNetwork {
513 chain_config,
514 gas_units,
515 } => {
516 let amount = (chain_config.gas_price * *gas_units as f32).ceil() as u128;
517 (new_coin(amount, &chain_config.gas_denom), *gas_units)
518 }
519 Self::RealCoin {
520 gas_coin,
521 gas_units,
522 } => (gas_coin.clone(), *gas_units),
523 };
524
525 Ok(layer_climb_proto::tx::Fee {
526 amount: vec![gas_coin],
527 gas_limit,
528 payer: "".to_string(),
529 granter: "".to_string(),
530 })
531 }
532}