alloy_network/transaction/
builder.rs

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