avail_rust_client/
transaction_options.rs

1//! Builders for configuring transaction submission defaults (nonce, tip, mortality).
2
3use crate::{Client, subxt_core::config::Header};
4use avail_rust_core::{AccountId, Era, ExtrinsicExtra, H256};
5
6/// Lightweight builder for composing extrinsic signing options.
7///
8/// All fields default to `None`, deferring to runtime-derived values during [`Options::build`].
9#[derive(Debug, Default, Clone, Copy)]
10pub struct Options {
11	/// Application identifier recorded in the signature payload.
12	pub app_id: Option<u32>,
13	/// Mortality configuration prior to refinement.
14	pub mortality: Option<MortalityOption>,
15	/// Nonce override to use during signing.
16	pub nonce: Option<u32>,
17	/// Tip (in smallest units) to attach to the extrinsic.
18	pub tip: Option<u128>,
19}
20
21impl Options {
22	/// Starts a builder with the provided application id.
23	///
24	/// # Arguments
25	/// * `app_id` - Application identifier recorded in the signature payload.
26	///
27	/// # Returns
28	/// Returns an [`Options`] builder seeded with the supplied application id.
29	pub fn new(app_id: u32) -> Self {
30		Self { app_id: Some(app_id), ..Default::default() }
31	}
32
33	/// Sets the application id recorded in the extrinsic.
34	///
35	/// # Arguments
36	/// * `value` - Application identifier to embed in the extrinsic.
37	///
38	/// # Returns
39	/// Returns the builder with the application id updated.
40	pub fn app_id(mut self, value: u32) -> Self {
41		self.app_id = Some(value);
42		self
43	}
44
45	/// Sets the mortality configuration for the extrinsic.
46	///
47	/// # Arguments
48	/// * `value` - Mortality option describing how long the extrinsic remains valid.
49	///
50	/// # Returns
51	/// Returns the builder with the mortality updated.
52	pub fn mortality(mut self, value: MortalityOption) -> Self {
53		self.mortality = Some(value);
54		self
55	}
56
57	/// Overrides the nonce to use when signing.
58	///
59	/// # Arguments
60	/// * `value` - Nonce that should be used when constructing the payload.
61	///
62	/// # Returns
63	/// Returns the builder with the nonce override applied.
64	pub fn nonce(mut self, value: u32) -> Self {
65		self.nonce = Some(value);
66		self
67	}
68
69	/// Overrides the tip to attach to the extrinsic.
70	///
71	/// # Arguments
72	/// * `value` - Tip (in smallest units) applied to the extrinsic.
73	///
74	/// # Returns
75	/// Returns the builder with the tip override applied.
76	pub fn tip(mut self, value: u128) -> Self {
77		self.tip = Some(value);
78		self
79	}
80
81	/// Resolves all builder values into concrete options ready for signing.
82	///
83	/// # Arguments
84	/// * `client` - Client used to fetch on-chain data when defaults are missing.
85	/// * `account_id` - Account whose nonce and mortality anchor are derived.
86	/// * `retry_on_error` - Optional override controlling retry behaviour for RPC calls.
87	///
88	/// # Returns
89	/// - `Ok(RefinedOptions)` containing explicit nonce, tip, app id, and mortality details.
90	/// - `Err(crate::Error)` when fetching account information or finality data fails.
91	///
92	/// # Errors
93	/// Returns `Err(crate::Error)` when RPC lookups required to refine options fail.
94	///
95	/// # Behaviour
96	/// - Missing nonce triggers an RPC call to fetch the account's next nonce.
97	/// - Missing mortality defaults to a 32-block period anchored at the latest finalized block.
98	/// - Missing app id and tip default to zero.
99	pub async fn build(
100		self,
101		client: &Client,
102		account_id: &AccountId,
103		retry_on_error: Option<bool>,
104	) -> Result<RefinedOptions, crate::Error> {
105		let app_id = self.app_id.unwrap_or_default();
106		let tip = self.tip.unwrap_or_default();
107		let nonce = match self.nonce {
108			Some(x) => x,
109			None => {
110				client
111					.chain()
112					.retry_on(retry_on_error, None)
113					.account_nonce(account_id.clone())
114					.await?
115			},
116		};
117		let mortality = self.mortality.unwrap_or(MortalityOption::Period(32));
118		let mortality = match mortality {
119			MortalityOption::Period(period) => RefinedMortality::from_period(client, period).await?,
120			MortalityOption::Full(mortality) => mortality,
121		};
122
123		Ok(RefinedOptions { app_id, mortality, nonce, tip })
124	}
125}
126
127/// Fully resolved transaction options used during signing.
128#[derive(Debug, Clone)]
129pub struct RefinedOptions {
130	/// Application identifier recorded in the extrinsic.
131	pub app_id: u32,
132	/// Fully resolved mortality parameters.
133	pub mortality: RefinedMortality,
134	/// Nonce applied to the extrinsic.
135	pub nonce: u32,
136	/// Tip (in smallest units) attached to the extrinsic.
137	pub tip: u128,
138}
139
140impl From<&RefinedOptions> for ExtrinsicExtra {
141	fn from(value: &RefinedOptions) -> Self {
142		let era = Era::mortal(value.mortality.period, value.mortality.block_height as u64);
143		ExtrinsicExtra {
144			era,
145			nonce: value.nonce,
146			tip: value.tip,
147			app_id: value.app_id,
148		}
149	}
150}
151
152/// User-facing mortality configuration options.
153#[derive(Debug, Clone, Copy)]
154pub enum MortalityOption {
155	/// Mortality based on a relative period (number of blocks) anchored at the finalized head.
156	Period(u64),
157	/// Fully specified mortality with explicit block hash and height.
158	Full(RefinedMortality),
159}
160
161/// Mortality with resolved block hash/height anchors.
162#[derive(Debug, Clone, Copy)]
163pub struct RefinedMortality {
164	/// Number of blocks before the extrinsic becomes invalid.
165	pub period: u64,
166	/// Block hash anchoring the mortality.
167	pub block_hash: H256,
168	/// Block height anchoring the mortality.
169	pub block_height: u32,
170}
171impl RefinedMortality {
172	/// Creates a refined mortality value.
173	///
174	/// # Arguments
175	/// * `period` - Number of blocks after which the extrinsic expires.
176	/// * `block_hash` - Block hash anchoring the mortality.
177	/// * `block_height` - Height corresponding to `block_hash`.
178	///
179	/// # Returns
180	/// Returns a [`RefinedMortality`] struct encapsulating the supplied values.
181	pub fn new(period: u64, block_hash: H256, block_height: u32) -> Self {
182		Self { period, block_hash, block_height }
183	}
184
185	/// Derives mortality from the latest finalized header using the given period.
186	///
187	/// # Arguments
188	/// * `client` - Client used to fetch the latest finalised header.
189	/// * `period` - Number of blocks after which the extrinsic expires.
190	///
191	/// # Errors
192	/// Returns `Err(crate::Error)` when fetching the finalized header fails.
193	pub async fn from_period(client: &Client, period: u64) -> Result<Self, crate::Error> {
194		let header = client.finalized().block_header().await?;
195		let (block_hash, block_height) = (header.hash(), header.number());
196		Ok(Self { period, block_hash, block_height })
197	}
198}