Skip to main content

alloy_network/transaction/
builder.rs

1use super::signer::NetworkWallet;
2use crate::Network;
3pub use alloy_network_primitives::{TransactionBuilder4844, TransactionBuilder7702};
4use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256};
5use alloy_rpc_types_eth::{AccessList, TransactionInputKind};
6use alloy_sol_types::SolCall;
7use futures_utils_wasm::impl_future;
8
9/// Result type for transaction builders
10pub type BuildResult<T, N> = Result<T, UnbuiltTransactionError<N>>;
11
12/// An unbuilt transaction, along with some error.
13#[derive(Debug, thiserror::Error)]
14#[error("Failed to build transaction: {error}")]
15pub struct UnbuiltTransactionError<N: Network> {
16    /// The original request that failed to build.
17    pub request: N::TransactionRequest,
18    /// The error that occurred.
19    #[source]
20    pub error: TransactionBuilderError<N>,
21}
22
23/// Error type for transaction builders.
24#[derive(Debug, thiserror::Error)]
25pub enum TransactionBuilderError<N: Network> {
26    /// Invalid transaction request
27    #[error("{0} transaction can't be built due to missing keys: {1:?}")]
28    InvalidTransactionRequest(N::TxType, Vec<&'static str>),
29
30    /// Signer cannot produce signature type required for transaction.
31    #[error("Signer cannot produce signature type required for transaction")]
32    UnsupportedSignatureType,
33
34    /// Signer error.
35    #[error(transparent)]
36    Signer(#[from] alloy_signer::Error),
37
38    /// A custom error.
39    #[error("{0}")]
40    Custom(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
41}
42
43impl<N: Network> TransactionBuilderError<N> {
44    /// Instantiate a custom error.
45    pub fn custom<E>(e: E) -> Self
46    where
47        E: std::error::Error + Send + Sync + 'static,
48    {
49        Self::Custom(Box::new(e))
50    }
51
52    /// Convert the error into an unbuilt transaction error.
53    pub const fn into_unbuilt(self, request: N::TransactionRequest) -> UnbuiltTransactionError<N> {
54        UnbuiltTransactionError { request, error: self }
55    }
56}
57
58/// Transaction builder.
59#[doc(alias = "TxBuilder")]
60pub trait TransactionBuilder: Default + Sized + Send + Sync + 'static {
61    /// Get the chain ID for the transaction.
62    fn chain_id(&self) -> Option<ChainId>;
63
64    /// Set the chain ID for the transaction.
65    fn set_chain_id(&mut self, chain_id: ChainId);
66
67    /// Builder-pattern method for setting the chain ID.
68    fn with_chain_id(mut self, chain_id: ChainId) -> Self {
69        self.set_chain_id(chain_id);
70        self
71    }
72
73    /// Get the nonce for the transaction.
74    fn nonce(&self) -> Option<u64>;
75
76    /// Set the nonce for the transaction.
77    fn set_nonce(&mut self, nonce: u64);
78
79    /// Takes the nonce out of the transaction, clearing it.
80    fn take_nonce(&mut self) -> Option<u64>;
81
82    /// Builder-pattern method for setting the nonce.
83    fn with_nonce(mut self, nonce: u64) -> Self {
84        self.set_nonce(nonce);
85        self
86    }
87
88    /// Takes the nonce out of the transaction, clearing it.
89    fn without_nonce(mut self) -> Self {
90        self.take_nonce();
91        self
92    }
93
94    /// Get the input data for the transaction.
95    fn input(&self) -> Option<&Bytes>;
96
97    /// Set the input data for the transaction.
98    fn set_input<T: Into<Bytes>>(&mut self, input: T);
99
100    /// Builder-pattern method for setting the input data.
101    fn with_input<T: Into<Bytes>>(mut self, input: T) -> Self {
102        self.set_input(input);
103        self
104    }
105
106    /// Set the input data for the transaction, respecting the input kind
107    fn set_input_kind<T: Into<Bytes>>(&mut self, input: T, _: TransactionInputKind) {
108        // forward all to input by default
109        self.set_input(input);
110    }
111
112    /// Builder-pattern method for setting the input data, respecting the input kind
113    fn with_input_kind<T: Into<Bytes>>(mut self, input: T, kind: TransactionInputKind) -> Self {
114        self.set_input_kind(input, kind);
115        self
116    }
117
118    /// Get the sender for the transaction.
119    fn from(&self) -> Option<Address>;
120
121    /// Set the sender for the transaction.
122    fn set_from(&mut self, from: Address);
123
124    /// Builder-pattern method for setting the sender.
125    fn with_from(mut self, from: Address) -> Self {
126        self.set_from(from);
127        self
128    }
129
130    /// Get the kind of transaction.
131    fn kind(&self) -> Option<TxKind>;
132
133    /// Clear the kind of transaction.
134    fn clear_kind(&mut self);
135
136    /// Set the kind of transaction.
137    fn set_kind(&mut self, kind: TxKind);
138
139    /// Builder-pattern method for setting the kind of transaction.
140    fn with_kind(mut self, kind: TxKind) -> Self {
141        self.set_kind(kind);
142        self
143    }
144
145    /// Get the recipient for the transaction.
146    fn to(&self) -> Option<Address> {
147        if let Some(TxKind::Call(addr)) = self.kind() {
148            return Some(addr);
149        }
150        None
151    }
152
153    /// Set the recipient for the transaction.
154    fn set_to(&mut self, to: Address) {
155        self.set_kind(to.into());
156    }
157
158    /// Builder-pattern method for setting the recipient.
159    fn with_to(mut self, to: Address) -> Self {
160        self.set_to(to);
161        self
162    }
163
164    /// Set the `to` field to a create call.
165    fn set_create(&mut self) {
166        self.set_kind(TxKind::Create);
167    }
168
169    /// Set the `to` field to a create call.
170    fn into_create(mut self) -> Self {
171        self.set_create();
172        self
173    }
174
175    /// Deploy the code by making a create call with data. This will set the
176    /// `to` field to [`TxKind::Create`].
177    fn set_deploy_code<T: Into<Bytes>>(&mut self, code: T) {
178        self.set_input(code.into());
179        self.set_create()
180    }
181
182    /// Deploy the code by making a create call with data. This will set the
183    /// `to` field to [`TxKind::Create`].
184    fn with_deploy_code<T: Into<Bytes>>(mut self, code: T) -> Self {
185        self.set_deploy_code(code);
186        self
187    }
188
189    /// Set the data field to a contract call. This will clear the `to` field
190    /// if it is set to [`TxKind::Create`].
191    fn set_call<T: SolCall>(&mut self, t: &T) {
192        self.set_input(t.abi_encode());
193        if matches!(self.kind(), Some(TxKind::Create)) {
194            self.clear_kind();
195        }
196    }
197
198    /// Make a contract call with data.
199    fn with_call<T: SolCall>(mut self, t: &T) -> Self {
200        self.set_call(t);
201        self
202    }
203
204    /// Calculates the address that will be created by the transaction, if any.
205    ///
206    /// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if
207    /// the `from` or `nonce` fields are not set.
208    fn calculate_create_address(&self) -> Option<Address> {
209        if !self.kind().is_some_and(|to| to.is_create()) {
210            return None;
211        }
212        let from = self.from()?;
213        let nonce = self.nonce()?;
214        Some(from.create(nonce))
215    }
216
217    /// Get the value for the transaction.
218    fn value(&self) -> Option<U256>;
219
220    /// Set the value for the transaction.
221    fn set_value(&mut self, value: U256);
222
223    /// Builder-pattern method for setting the value.
224    fn with_value(mut self, value: U256) -> Self {
225        self.set_value(value);
226        self
227    }
228
229    /// Get the legacy gas price for the transaction.
230    fn gas_price(&self) -> Option<u128>;
231
232    /// Set the legacy gas price for the transaction.
233    fn set_gas_price(&mut self, gas_price: u128);
234
235    /// Builder-pattern method for setting the legacy gas price.
236    fn with_gas_price(mut self, gas_price: u128) -> Self {
237        self.set_gas_price(gas_price);
238        self
239    }
240
241    /// Get the max fee per gas for the transaction.
242    fn max_fee_per_gas(&self) -> Option<u128>;
243
244    /// Set the max fee per gas  for the transaction.
245    fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128);
246
247    /// Builder-pattern method for setting max fee per gas .
248    fn with_max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
249        self.set_max_fee_per_gas(max_fee_per_gas);
250        self
251    }
252
253    /// Get the max priority fee per gas for the transaction.
254    fn max_priority_fee_per_gas(&self) -> Option<u128>;
255
256    /// Set the max priority fee per gas for the transaction.
257    fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128);
258
259    /// Builder-pattern method for setting max priority fee per gas.
260    fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
261        self.set_max_priority_fee_per_gas(max_priority_fee_per_gas);
262        self
263    }
264    /// Get the gas limit for the transaction.
265    fn gas_limit(&self) -> Option<u64>;
266
267    /// Set the gas limit for the transaction.
268    fn set_gas_limit(&mut self, gas_limit: u64);
269
270    /// Builder-pattern method for setting the gas limit.
271    fn with_gas_limit(mut self, gas_limit: u64) -> Self {
272        self.set_gas_limit(gas_limit);
273        self
274    }
275
276    /// Get the EIP-2930 access list for the transaction.
277    fn access_list(&self) -> Option<&AccessList>;
278
279    /// Sets the EIP-2930 access list.
280    fn set_access_list(&mut self, access_list: AccessList);
281
282    /// Builder-pattern method for setting the access list.
283    fn with_access_list(mut self, access_list: AccessList) -> Self {
284        self.set_access_list(access_list);
285        self
286    }
287
288    /// Apply a function to the builder, returning the modified builder.
289    fn apply<F>(self, f: F) -> Self
290    where
291        F: FnOnce(Self) -> Self,
292    {
293        f(self)
294    }
295
296    /// Apply a fallible function to the builder, returning the modified builder or an error.
297    fn try_apply<F, E>(self, f: F) -> Result<Self, E>
298    where
299        F: FnOnce(Self) -> Result<Self, E>,
300    {
301        f(self)
302    }
303}
304
305/// Network-specific transaction builder.
306///
307/// Extends [`TransactionBuilder`] with [`build_unsigned`](Self::build_unsigned)
308/// and [`build`](Self::build) methods.
309#[doc(alias = "NetworkTxBuilder")]
310pub trait NetworkTransactionBuilder<N: Network>: TransactionBuilder {
311    /// True if the builder contains all necessary information to be submitted
312    /// to the `eth_sendTransaction` endpoint.
313    fn can_submit(&self) -> bool;
314
315    /// True if the builder contains all necessary information to be built into
316    /// a valid transaction.
317    fn can_build(&self) -> bool;
318
319    /// Check if all necessary keys are present to build the specified type,
320    /// returning a list of missing keys.
321    fn complete_type(&self, ty: N::TxType) -> Result<(), Vec<&'static str>>;
322
323    /// Check if all necessary keys are present to build the currently-preferred
324    /// transaction type, returning a list of missing keys.
325    fn complete_preferred(&self) -> Result<(), Vec<&'static str>> {
326        self.complete_type(self.output_tx_type())
327    }
328
329    /// Assert that the builder prefers a certain transaction type. This does
330    /// not indicate that the builder is ready to build. This function uses a
331    /// `dbg_assert_eq!` to check the builder status, and will have no affect
332    /// in release builds.
333    fn assert_preferred(&self, ty: N::TxType) {
334        debug_assert_eq!(self.output_tx_type(), ty);
335    }
336
337    /// Assert that the builder prefers a certain transaction type. This does
338    /// not indicate that the builder is ready to build. This function uses a
339    /// `dbg_assert_eq!` to check the builder status, and will have no affect
340    /// in release builds.
341    fn assert_preferred_chained(self, ty: N::TxType) -> Self {
342        self.assert_preferred(ty);
343        self
344    }
345
346    /// Returns the transaction type that this builder will attempt to build.
347    /// This does not imply that the builder is ready to build.
348    #[doc(alias = "output_transaction_type")]
349    fn output_tx_type(&self) -> N::TxType;
350
351    /// Returns the transaction type that this builder will build. `None` if
352    /// the builder is not ready to build.
353    #[doc(alias = "output_transaction_type_checked")]
354    fn output_tx_type_checked(&self) -> Option<N::TxType>;
355
356    /// Trim any conflicting keys and populate any computed fields (like blob
357    /// hashes).
358    ///
359    /// This is useful for transaction requests that have multiple conflicting
360    /// fields. While these may be buildable, they may not be submitted to the
361    /// RPC. This method should be called before RPC submission, but is not
362    /// necessary before building.
363    fn prep_for_submission(&mut self);
364
365    /// Build an unsigned, but typed, transaction.
366    fn build_unsigned(self) -> BuildResult<N::UnsignedTx, N>;
367
368    /// Build a signed transaction.
369    fn build<W: NetworkWallet<N>>(
370        self,
371        wallet: &W,
372    ) -> impl_future!(<Output = Result<N::TxEnvelope, TransactionBuilderError<N>>>);
373}