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_address::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: &layer_climb_proto::tx::TxBody,
209 ) -> Result<layer_climb_proto::abci::GasInfo> {
210 let fee = FeeCalculation::Simulation {
211 chain_config: &self.querier.chain_config,
212 }
213 .calculate()?;
214
215 let simulate_tx_resp = self
216 .querier
217 .simulate_tx(
218 self.sign_tx(signer_info, account_number, tx_body, fee, true)
219 .await?,
220 )
221 .await?;
222
223 simulate_tx_resp
224 .gas_info
225 .context("unable to get gas from simulation")
226 }
227
228 pub async fn broadcast_raw(
232 self,
233 messages: Vec<layer_climb_proto::Any>,
234 ) -> Result<AnyTxResponse> {
235 let block_height = self.querier.block_height().await?;
236
237 let tx_timeout_blocks = self
238 .tx_timeout_blocks
239 .unwrap_or(Self::DEFAULT_TX_TIMEOUT_BLOCKS);
240
241 let mut body = layer_climb_proto::tx::TxBody {
242 messages,
243 memo: self.memo.as_deref().unwrap_or("").to_string(),
244 timeout_height: block_height + tx_timeout_blocks,
245 extension_options: Default::default(),
246 non_critical_extension_options: Default::default(),
247 };
248
249 if let Some(middleware) = self.middleware_map_body.as_ref() {
250 for middleware in middleware.iter() {
251 body = match middleware.map_body(body).await {
252 Ok(req) => req,
253 Err(e) => return Err(e),
254 }
255 }
256 }
257
258 let mut base_account: Option<layer_climb_proto::auth::BaseAccount> = None;
259
260 let sequence = match &self.sequence_strategy {
261 Some(sequence_strategy) => match sequence_strategy.kind {
262 SequenceStrategyKind::Query => {
263 base_account = Some(self.query_base_account().await?);
264 base_account.as_ref().unwrap().sequence
265 }
266 SequenceStrategyKind::QueryAndIncrement => {
267 if !sequence_strategy
268 .has_queried
269 .load(std::sync::atomic::Ordering::SeqCst)
270 {
271 base_account = Some(self.query_base_account().await?);
272 sequence_strategy
273 .has_queried
274 .store(true, std::sync::atomic::Ordering::SeqCst);
275 sequence_strategy.value.store(
276 base_account.as_ref().unwrap().sequence,
277 std::sync::atomic::Ordering::SeqCst,
278 );
279 base_account.as_ref().unwrap().sequence
280 } else {
281 sequence_strategy
282 .value
283 .load(std::sync::atomic::Ordering::SeqCst)
284 }
285 }
286 SequenceStrategyKind::SetAndIncrement(_) => sequence_strategy
287 .value
288 .load(std::sync::atomic::Ordering::SeqCst),
289 SequenceStrategyKind::Constant(n) => n,
290 },
291 None => {
292 base_account = Some(self.query_base_account().await?);
293 base_account.as_ref().unwrap().sequence
294 }
295 };
296
297 let account_number = match self.account_number {
298 Some(account_number) => account_number,
299 None => match base_account {
300 Some(base_account) => base_account.account_number,
301 None => self.query_base_account().await?.account_number,
302 },
303 };
304
305 let gas_units = match self.gas_units_or_simulate {
306 Some(gas_units) => gas_units,
307 None => {
308 let gas_multiplier = self
309 .gas_simulate_multiplier
310 .unwrap_or(Self::DEFAULT_GAS_MULTIPLIER);
311
312 let signer_info = self
313 .signer
314 .signer_info(sequence, layer_climb_proto::tx::SignMode::Unspecified)
315 .await?;
316
317 let gas_info = self
318 .simulate_gas(signer_info, account_number, &body)
319 .await?;
320
321 (gas_info.gas_used as f32 * gas_multiplier).ceil() as u64
322 }
323 };
324
325 let fee = match self.gas_coin.clone() {
326 Some(gas_coin) => FeeCalculation::RealCoin {
327 gas_coin,
328 gas_units,
329 }
330 .calculate()?,
331 None => FeeCalculation::RealNetwork {
332 chain_config: &self.querier.chain_config,
333 gas_units,
334 }
335 .calculate()?,
336 };
337
338 let signer_info = self
339 .signer
340 .signer_info(sequence, layer_climb_proto::tx::SignMode::Direct)
341 .await?;
342
343 let tx_bytes = self
344 .sign_tx(signer_info, account_number, &body, fee, false)
345 .await?;
346 let broadcast_mode = self.broadcast_mode.unwrap_or(Self::DEFAULT_BROADCAST_MODE);
347
348 let tx_response = self
349 .querier
350 .broadcast_tx_bytes(tx_bytes, broadcast_mode)
351 .await?;
352
353 if let Some(sequence) = self.sequence_strategy {
355 match sequence.kind {
356 SequenceStrategyKind::QueryAndIncrement => {
357 sequence
358 .value
359 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
360 }
361 SequenceStrategyKind::SetAndIncrement(_) => {
362 sequence
363 .value
364 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
365 }
366 _ => {}
367 }
368 }
369
370 if tx_response.code() != 0 {
371 bail!(
372 "tx failed with code: {}, codespace: {}, raw_log: {}",
373 tx_response.code(),
374 tx_response.codespace(),
375 tx_response.raw_log()
376 );
377 }
378
379 let mut tx_response = if self.broadcast_poll {
380 let sleep_duration = self
381 .broadcast_poll_sleep_duration
382 .unwrap_or(Self::DEFAULT_BROADCAST_POLL_SLEEP_DURATION);
383 let timeout_duration = self
384 .broadcast_poll_timeout_duration
385 .unwrap_or(Self::DEFAULT_BROADCAST_POLL_TIMEOUT_DURATION);
386
387 AnyTxResponse::Abci(
388 self.querier
389 .poll_until_tx_ready(tx_response.tx_hash(), sleep_duration, timeout_duration)
390 .await?
391 .tx_response,
392 )
393 } else {
394 tx_response
395 };
396
397 if tx_response.code() != 0 {
398 bail!(
399 "tx failed with code: {}, codespace: {}, raw_log: {}",
400 tx_response.code(),
401 tx_response.codespace(),
402 tx_response.raw_log()
403 );
404 }
405
406 if let Some(middleware) = self.middleware_map_resp.as_ref() {
407 for middleware in middleware.iter() {
408 tx_response = match middleware.map_resp(tx_response).await {
409 Ok(req) => req,
410 Err(e) => return Err(e),
411 }
412 }
413 }
414
415 Ok(tx_response)
416 }
417
418 async fn sign_tx(
419 &self,
420 signer_info: layer_climb_proto::tx::SignerInfo,
421 account_number: u64,
422 body: &layer_climb_proto::tx::TxBody,
423 fee: layer_climb_proto::tx::Fee,
424 simulate_only: bool,
425 ) -> Result<Vec<u8>> {
426 #[allow(deprecated)]
427 let auth_info = layer_climb_proto::tx::AuthInfo {
428 signer_infos: vec![signer_info],
429 fee: Some(fee),
430 tip: None,
431 };
432
433 let sign_doc = layer_climb_proto::tx::SignDoc {
434 body_bytes: proto_into_bytes(body)?,
435 auth_info_bytes: proto_into_bytes(&auth_info)?,
436 chain_id: self.querier.chain_config.chain_id.to_string(),
437 account_number,
438 };
439
440 let signature = match simulate_only {
441 true => Vec::new(),
442 false => self.signer.sign(&sign_doc).await?,
443 };
444
445 let tx_raw = layer_climb_proto::tx::TxRaw {
446 body_bytes: sign_doc.body_bytes.clone(),
447 auth_info_bytes: sign_doc.auth_info_bytes.clone(),
448 signatures: vec![signature],
449 };
450
451 proto_into_bytes(&tx_raw)
452 }
453}
454
455#[derive(Clone, Debug)]
456pub struct SequenceStrategy {
457 pub kind: SequenceStrategyKind,
458 pub value: Arc<AtomicU64>,
459 pub has_queried: Arc<AtomicBool>,
460}
461
462impl SequenceStrategy {
463 pub fn new(kind: SequenceStrategyKind) -> Self {
464 Self {
465 value: Arc::new(AtomicU64::new(match kind {
466 SequenceStrategyKind::Query => 0, SequenceStrategyKind::QueryAndIncrement => 0, SequenceStrategyKind::SetAndIncrement(n) => n,
469 SequenceStrategyKind::Constant(n) => n,
470 })),
471 kind,
472 has_queried: Arc::new(AtomicBool::new(false)),
473 }
474 }
475}
476
477#[derive(Clone, Debug)]
478pub enum SequenceStrategyKind {
479 Query,
481 QueryAndIncrement,
483 SetAndIncrement(u64),
485 Constant(u64),
487}
488
489pub enum FeeCalculation<'a> {
490 Simulation {
491 chain_config: &'a ChainConfig,
492 },
493 RealNetwork {
494 chain_config: &'a ChainConfig,
495 gas_units: u64,
496 },
497 RealCoin {
498 gas_coin: layer_climb_proto::Coin,
499 gas_units: u64,
500 },
501}
502
503impl FeeCalculation<'_> {
504 pub fn calculate(&self) -> Result<layer_climb_proto::tx::Fee> {
505 let (gas_coin, gas_limit) = match self {
506 Self::Simulation { chain_config } => (new_coin(0, &chain_config.gas_denom), 0),
507 Self::RealNetwork {
508 chain_config,
509 gas_units,
510 } => {
511 let amount = (chain_config.gas_price * *gas_units as f32).ceil() as u128;
512 (new_coin(amount, &chain_config.gas_denom), *gas_units)
513 }
514 Self::RealCoin {
515 gas_coin,
516 gas_units,
517 } => (gas_coin.clone(), *gas_units),
518 };
519
520 Ok(layer_climb_proto::tx::Fee {
521 amount: vec![gas_coin],
522 gas_limit,
523 payer: "".to_string(),
524 granter: "".to_string(),
525 })
526 }
527}