avail_rust_client/submission/submittable.rs
1use crate::{Client, Error, subxt_signer::sr25519::Keypair, transaction_options::Options};
2use avail_rust_core::{
3 H256, HasHeader, RpcError,
4 ext::codec::Encode,
5 substrate::extrinsic::{ExtrinsicCall, GenericExtrinsic},
6 types::substrate::{FeeDetails, RuntimeDispatchInfo},
7};
8
9/// Builder that keeps an encoded call together with the client connection and exposes helpers for
10/// signing, submitting, and querying execution costs.
11#[derive(Clone)]
12pub struct SubmittableTransaction {
13 client: Client,
14 pub call: ExtrinsicCall,
15 retry_on_error: Option<bool>,
16}
17
18impl SubmittableTransaction {
19 /// Creates a transaction builder from an encoded call.
20 ///
21 /// The builder is inert until one of the async helpers is invoked. By default it inherits the
22 /// client's retry policy, but this can be customised via [`set_retry_on_error`](Self::set_retry_on_error).
23 pub fn new(client: Client, call: ExtrinsicCall) -> Self {
24 Self { client, call, retry_on_error: None }
25 }
26
27 /// Signs the call with the provided keypair and submits it to the chain in a single RPC round-trip.
28 ///
29 /// # Returns
30 /// - `Ok(SubmittedTransaction)` when the node accepts the extrinsic and returns its hash along with
31 /// metadata inferred from `options`.
32 /// - `Err(Error)` when signing fails, submission is rejected by the node, or any underlying RPC call
33 /// (potentially retried according to the configured policy) returns an error.
34 ///
35 /// The submission uses `options` (nonce, tip, mortality) exactly as provided; no additional mutation
36 /// happens inside this helper.
37 pub async fn sign_and_submit(
38 &self,
39 signer: &Keypair,
40 options: Options,
41 ) -> Result<super::SubmittedTransaction, Error> {
42 self.client
43 .chain()
44 .retry_on(self.retry_on_error, None)
45 .sign_and_submit_call(signer, &self.call, options)
46 .await
47 }
48
49 /// Signs the call without submitting it, returning the encoded extrinsic bytes that would be sent
50 /// to the network.
51 ///
52 /// # Returns
53 /// - `Ok(GenericExtrinsic<'_>)` containing the SCALE-encoded payload ready for submission.
54 /// - `Err(Error)` when the signing operation fails (for example, due to a bad signer, stale
55 /// account information, or RPC issues while fetching metadata).
56 pub async fn sign(&self, signer: &Keypair, options: Options) -> Result<GenericExtrinsic<'_>, Error> {
57 self.client
58 .chain()
59 .retry_on(self.retry_on_error, None)
60 .build_extrinsic_from_call(signer, &self.call, options)
61 .await
62 }
63
64 /// Estimates fee details for the underlying call using runtime information at `at` without signing
65 /// or submitting anything.
66 ///
67 /// # Returns
68 /// - `Ok(FeeDetails)` containing the partial fee breakdown the runtime reports for the call.
69 /// - `Err(RpcError)` if the node rejects the dry-run query (e.g. bad call data, missing runtime
70 /// exposes) or if transport errors occur.
71 pub async fn estimate_call_fees(&self, at: Option<H256>) -> Result<FeeDetails, RpcError> {
72 let call = self.call.encode();
73 self.client
74 .chain()
75 .retry_on(self.retry_on_error, None)
76 .transaction_payment_query_call_fee_details(call, at)
77 .await
78 }
79
80 /// Signs the call with the provided options and queries the chain for the cost of submitting that
81 /// exact extrinsic.
82 ///
83 /// # Returns
84 /// - `Ok(FeeDetails)` containing the fee components returned by the runtime.
85 /// - `Err(Error)` if signing the call fails or if the fee query returns an error (in which case the
86 /// underlying [`RpcError`] is wrapped in the returned [`Error`]).
87 pub async fn estimate_extrinsic_fees(
88 &self,
89 signer: &Keypair,
90 options: Options,
91 at: Option<H256>,
92 ) -> Result<FeeDetails, Error> {
93 let transaction = self.sign(signer, options).await?;
94 let transaction = transaction.encode();
95 Ok(self
96 .client
97 .chain()
98 .retry_on(self.retry_on_error, None)
99 .transaction_payment_query_fee_details(transaction, at)
100 .await?)
101 }
102
103 /// Returns runtime dispatch information for the call, including weight, class, and partial fee
104 /// estimation based on the provided block context.
105 ///
106 /// # Returns
107 /// - `Ok(RuntimeDispatchInfo)` with weight and class metadata.
108 /// - `Err(RpcError)` if the node cannot evaluate the call (bad parameters, runtime error, or RPC
109 /// transport failure).
110 pub async fn call_info(&self, at: Option<H256>) -> Result<RuntimeDispatchInfo, RpcError> {
111 let call = self.call.encode();
112 self.client
113 .chain()
114 .retry_on(self.retry_on_error, None)
115 .transaction_payment_query_call_info(call, at)
116 .await
117 }
118
119 /// Resolves whether RPC calls performed through this builder should be retried on transient
120 /// failures.
121 ///
122 /// The method returns the explicit override set by [`set_retry_on_error`](Self::set_retry_on_error),
123 /// falling back to the client's global retry configuration when no override is present.
124 pub fn should_retry_on_error(&self) -> bool {
125 self.retry_on_error
126 .unwrap_or_else(|| self.client.is_global_retries_enabled())
127 }
128
129 /// Controls retry behaviour for RPC calls sent via this builder.
130 ///
131 /// # Parameters
132 /// - `Some(true)`: force retries regardless of the client's global setting.
133 /// - `Some(false)`: disable retries for requests issued through this builder.
134 /// - `None`: fall back to the client's global retry configuration.
135 pub fn set_retry_on_error(&mut self, value: Option<bool>) {
136 self.retry_on_error = value;
137 }
138
139 /// Converts any encodable call into a `SubmittableTransaction` based on its pallet and call indices.
140 /// The provided value is SCALE-encoded immediately; failures propagate as panics originating from
141 /// the underlying encoding implementation.
142 pub fn from_encodable<T: HasHeader + Encode>(client: Client, value: T) -> SubmittableTransaction {
143 let call = ExtrinsicCall::new(T::HEADER_INDEX.0, T::HEADER_INDEX.1, value.encode());
144 SubmittableTransaction::new(client, call)
145 }
146
147 /// Hashes the call payload as it would appear in an extrinsic, returning the blake2 hash used by
148 /// the runtime for call identification.
149 pub fn call_hash(&self) -> H256 {
150 H256::from(self.call.hash())
151 }
152}
153
154impl From<SubmittableTransaction> for ExtrinsicCall {
155 fn from(value: SubmittableTransaction) -> Self {
156 value.call
157 }
158}
159
160impl From<&SubmittableTransaction> for ExtrinsicCall {
161 fn from(value: &SubmittableTransaction) -> Self {
162 value.call.clone()
163 }
164}