1use crate::{
4 Client, Error, UserError,
5 block_api::{
6 BlockApi, BlockEvents, BlockExtrinsic, BlockRawExtrinsic, BlockTransaction, BlockWithExt, BlockWithRawExt,
7 BlockWithTx, ExtrinsicEvents,
8 },
9 subscription::Sub,
10 subxt_signer::sr25519::Keypair,
11 transaction_options::{Options, RefinedMortality, RefinedOptions},
12};
13use avail_rust_core::{
14 AccountId, BlockInfo, EncodeSelector, H256, HasHeader, RpcError,
15 ext::codec::Encode,
16 substrate::extrinsic::{ExtrinsicAdditional, ExtrinsicCall, GenericExtrinsic},
17 types::{
18 metadata::{HashString, TransactionRef},
19 substrate::{FeeDetails, RuntimeDispatchInfo},
20 },
21};
22use codec::Decode;
23#[cfg(feature = "tracing")]
24use tracing::info;
25
26#[derive(Clone)]
29pub struct SubmittableTransaction {
30 client: Client,
31 pub call: ExtrinsicCall,
32 retry_on_error: Option<bool>,
33}
34
35impl SubmittableTransaction {
36 pub fn new(client: Client, call: ExtrinsicCall) -> Self {
41 Self { client, call, retry_on_error: None }
42 }
43
44 pub async fn sign_and_submit(&self, signer: &Keypair, options: Options) -> Result<SubmittedTransaction, Error> {
55 self.client
56 .chain()
57 .retry_on(self.retry_on_error, None)
58 .sign_and_submit_call(signer, &self.call, options)
59 .await
60 }
61
62 pub async fn sign(&self, signer: &Keypair, options: Options) -> Result<GenericExtrinsic<'_>, Error> {
70 self.client
71 .chain()
72 .retry_on(self.retry_on_error, None)
73 .sign_call(signer, &self.call, options)
74 .await
75 }
76
77 pub async fn estimate_call_fees(&self, at: Option<H256>) -> Result<FeeDetails, RpcError> {
85 let call = self.call.encode();
86 self.client
87 .chain()
88 .retry_on(self.retry_on_error, None)
89 .transaction_payment_query_call_fee_details(call, at)
90 .await
91 }
92
93 pub async fn estimate_extrinsic_fees(
101 &self,
102 signer: &Keypair,
103 options: Options,
104 at: Option<H256>,
105 ) -> Result<FeeDetails, Error> {
106 let transaction = self.sign(signer, options).await?;
107 let transaction = transaction.encode();
108 Ok(self
109 .client
110 .chain()
111 .retry_on(self.retry_on_error, None)
112 .transaction_payment_query_fee_details(transaction, at)
113 .await?)
114 }
115
116 pub async fn call_info(&self, at: Option<H256>) -> Result<RuntimeDispatchInfo, RpcError> {
124 let call = self.call.encode();
125 self.client
126 .chain()
127 .retry_on(self.retry_on_error, None)
128 .transaction_payment_query_call_info(call, at)
129 .await
130 }
131
132 pub fn should_retry_on_error(&self) -> bool {
138 should_retry(&self.client, self.retry_on_error)
139 }
140
141 pub fn set_retry_on_error(&mut self, value: Option<bool>) {
148 self.retry_on_error = value;
149 }
150
151 pub fn from_encodable<T: HasHeader + Encode>(client: Client, value: T) -> SubmittableTransaction {
155 let call = ExtrinsicCall::new(T::HEADER_INDEX.0, T::HEADER_INDEX.1, value.encode());
156 SubmittableTransaction::new(client, call)
157 }
158
159 pub fn call_hash(&self) -> [u8; 32] {
162 self.call.hash()
163 }
164}
165
166impl From<SubmittableTransaction> for ExtrinsicCall {
167 fn from(value: SubmittableTransaction) -> Self {
168 value.call
169 }
170}
171
172impl From<&SubmittableTransaction> for ExtrinsicCall {
173 fn from(value: &SubmittableTransaction) -> Self {
174 value.call.clone()
175 }
176}
177
178#[derive(Clone)]
181pub struct SubmittedTransaction {
182 client: Client,
183 pub tx_hash: H256,
184 pub account_id: AccountId,
185 pub options: RefinedOptions,
186 pub additional: ExtrinsicAdditional,
187}
188
189impl SubmittedTransaction {
190 pub fn new(
195 client: Client,
196 tx_hash: H256,
197 account_id: AccountId,
198 options: RefinedOptions,
199 additional: ExtrinsicAdditional,
200 ) -> Self {
201 Self { client, tx_hash, account_id, options, additional }
202 }
203
204 pub async fn receipt(&self, use_best_block: bool) -> Result<Option<TransactionReceipt>, Error> {
215 Utils::transaction_receipt(
216 self.client.clone(),
217 self.tx_hash,
218 self.options.nonce,
219 &self.account_id,
220 &self.options.mortality,
221 use_best_block,
222 )
223 .await
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231#[repr(u8)]
232pub enum BlockState {
233 Included = 0,
235 Finalized = 1,
237 Discarded = 2,
239 DoesNotExist = 3,
241}
242
243#[derive(Clone)]
245pub struct TransactionReceipt {
246 client: Client,
247 pub block_ref: BlockInfo,
248 pub tx_ref: TransactionRef,
249}
250
251impl TransactionReceipt {
252 pub fn new(client: Client, block: BlockInfo, tx: TransactionRef) -> Self {
254 Self { client, block_ref: block, tx_ref: tx }
255 }
256
257 pub async fn block_state(&self) -> Result<BlockState, Error> {
263 self.client.chain().block_state(self.block_ref.hash).await
264 }
265
266 pub async fn tx<T: HasHeader + Decode>(&self) -> Result<BlockTransaction<T>, Error> {
272 let block = BlockWithTx::new(self.client.clone(), self.block_ref.height);
273 let tx = block.get(self.tx_ref.index).await?;
274 let Some(tx) = tx else {
275 return Err(RpcError::ExpectedData("No transaction found at the requested index.".into()).into());
276 };
277
278 Ok(tx)
279 }
280
281 pub async fn ext<T: HasHeader + Decode>(&self) -> Result<BlockExtrinsic<T>, Error> {
287 let block = BlockWithExt::new(self.client.clone(), self.block_ref.height);
288 let ext: Option<BlockExtrinsic<T>> = block.get(self.tx_ref.index).await?;
289 let Some(ext) = ext else {
290 return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
291 };
292
293 Ok(ext)
294 }
295
296 pub async fn call<T: HasHeader + Decode>(&self) -> Result<T, Error> {
302 let block = BlockWithExt::new(self.client.clone(), self.block_ref.height);
303 let tx = block.get(self.tx_ref.index).await?;
304 let Some(tx) = tx else {
305 return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
306 };
307
308 Ok(tx.call)
309 }
310
311 pub async fn raw_ext(&self, encode_as: EncodeSelector) -> Result<BlockRawExtrinsic, Error> {
317 let block = BlockWithRawExt::new(self.client.clone(), self.block_ref.height);
318 let ext = block.get(self.tx_ref.index, encode_as).await?;
319 let Some(ext) = ext else {
320 return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
321 };
322
323 Ok(ext)
324 }
325
326 pub async fn events(&self) -> Result<ExtrinsicEvents, Error> {
332 let block = BlockEvents::new(self.client.clone(), self.block_ref.hash);
333 let events = block.ext(self.tx_ref.index).await?;
334 let Some(events) = events else {
335 return Err(RpcError::ExpectedData("No events found for the requested extrinsic.".into()).into());
336 };
337 Ok(events)
338 }
339
340 pub async fn from_range(
351 client: Client,
352 tx_hash: impl Into<HashString>,
353 block_start: u32,
354 block_end: u32,
355 use_best_block: bool,
356 ) -> Result<Option<TransactionReceipt>, Error> {
357 if block_start > block_end {
358 return Err(UserError::ValidationFailed("Block Start cannot start after Block End".into()).into());
359 }
360 let tx_hash: HashString = tx_hash.into();
361 let mut sub = Sub::new(client.clone());
362 sub.use_best_block(use_best_block);
363 sub.set_block_height(block_start);
364
365 loop {
366 let block_ref = sub.next().await?;
367
368 let block = BlockWithRawExt::new(client.clone(), block_ref.height);
369 let ext = block.get(tx_hash.clone(), EncodeSelector::None).await?;
370 if let Some(ext) = ext {
371 let tr = TransactionReceipt::new(client.clone(), block_ref, (ext.ext_hash(), ext.ext_index()).into());
372 return Ok(Some(tr));
373 }
374
375 if block_ref.height >= block_end {
376 return Ok(None);
377 }
378 }
379 }
380}
381
382pub struct Utils;
384impl Utils {
385 pub async fn transaction_receipt(
392 client: Client,
393 tx_hash: H256,
394 nonce: u32,
395 account_id: &AccountId,
396 mortality: &RefinedMortality,
397 use_best_block: bool,
398 ) -> Result<Option<TransactionReceipt>, Error> {
399 let Some(block_ref) =
400 Self::find_correct_block_info(&client, nonce, tx_hash, account_id, mortality, use_best_block).await?
401 else {
402 return Ok(None);
403 };
404
405 let block = BlockApi::new(client.clone(), block_ref.hash);
406 let ext = block.raw_ext().get(tx_hash, EncodeSelector::None).await?;
407
408 let Some(ext) = ext else {
409 return Ok(None);
410 };
411
412 let tx_ref = TransactionRef::from((ext.ext_hash(), ext.ext_index()));
413 Ok(Some(TransactionReceipt::new(client, block_ref, tx_ref)))
414 }
415
416 pub async fn find_correct_block_info(
427 client: &Client,
428 nonce: u32,
429 tx_hash: H256,
430 account_id: &AccountId,
431 mortality: &RefinedMortality,
432 use_best_block: bool,
433 ) -> Result<Option<BlockInfo>, Error> {
434 let mortality_ends_height = mortality.block_height + mortality.period as u32;
435
436 let mut sub = Sub::new(client.clone());
437 sub.set_block_height(mortality.block_height);
438 sub.use_best_block(use_best_block);
439
440 let mut current_block_height = mortality.block_height;
441
442 #[cfg(feature = "tracing")]
443 {
444 match use_best_block {
445 true => {
446 let info = client.best().block_info().await?;
447 info!(target: "lib", "Nonce: {} Account address: {} Current Best Height: {} Mortality End Height: {}", nonce, account_id, info.height, mortality_ends_height);
448 },
449 false => {
450 let info = client.finalized().block_info().await?;
451 info!(target: "lib", "Nonce: {} Account address: {} Current Finalized Height: {} Mortality End Height: {}", nonce, account_id, info.height, mortality_ends_height);
452 },
453 };
454 }
455
456 while mortality_ends_height >= current_block_height {
457 let info = sub.next().await?;
458 current_block_height = info.height;
459
460 let state_nonce = client.chain().block_nonce(account_id.clone(), info.hash).await?;
461 if state_nonce > nonce {
462 trace_new_block(nonce, state_nonce, account_id, info, true);
463 return Ok(Some(info));
464 }
465 if state_nonce == 0 {
466 let block = BlockApi::new(client.clone(), info.hash);
467 let ext = block.raw_ext().get(tx_hash, EncodeSelector::None).await?;
468 if ext.is_some() {
469 trace_new_block(nonce, state_nonce, account_id, info, true);
470 return Ok(Some(info));
471 }
472 }
473
474 trace_new_block(nonce, state_nonce, account_id, info, false);
475 }
476
477 Ok(None)
478 }
479}
480
481fn trace_new_block(nonce: u32, state_nonce: u32, account_id: &AccountId, block_info: BlockInfo, search_done: bool) {
486 #[cfg(feature = "tracing")]
487 {
488 if search_done {
489 info!(target: "lib", "Account ({}, {}). At block ({}, {:?}) found nonce: {}. Search is done", nonce, account_id, block_info.height, block_info.hash, state_nonce);
490 } else {
491 info!(target: "lib", "Account ({}, {}). At block ({}, {:?}) found nonce: {}.", nonce, account_id, block_info.height, block_info.hash, state_nonce);
492 }
493 }
494
495 #[cfg(not(feature = "tracing"))]
496 {
497 let _ = (nonce, state_nonce, account_id, block_info, search_done);
498 }
499}
500
501fn should_retry(client: &Client, value: Option<bool>) -> bool {
505 value.unwrap_or(client.is_global_retries_enabled())
506}