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}