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}