alloy_consensus/transaction/
mod.rs

1//! Transaction types.
2
3use crate::Signed;
4use alloc::vec::Vec;
5use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
6use alloy_primitives::{keccak256, Address, Bytes, ChainId, Selector, TxKind, B256, U256};
7use core::{any, fmt};
8
9mod eip1559;
10pub use eip1559::TxEip1559;
11
12mod eip2930;
13pub use eip2930::TxEip2930;
14
15mod eip7702;
16pub use eip7702::TxEip7702;
17
18/// [EIP-4844] constants, helpers, and types.
19pub mod eip4844;
20pub mod pooled;
21pub use pooled::PooledTransaction;
22
23use alloy_eips::eip4844::DATA_GAS_PER_BLOB;
24pub use alloy_eips::eip4844::{
25    builder::{SidecarBuilder, SidecarCoder, SimpleCoder},
26    utils as eip4844_utils, Blob, BlobTransactionSidecar, Bytes48,
27};
28#[cfg(feature = "kzg")]
29pub use eip4844::BlobTransactionValidationError;
30pub use eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
31
32/// Re-export for convenience
33pub use either::Either;
34
35mod envelope;
36pub use envelope::{TxEnvelope, TxType};
37
38mod legacy;
39pub use legacy::{from_eip155_value, to_eip155_value, TxLegacy};
40
41mod rlp;
42#[doc(hidden)]
43pub use rlp::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, RlpEcdsaTx};
44
45mod typed;
46pub use typed::{EthereumTypedTransaction, TypedTransaction};
47
48mod meta;
49pub use meta::{TransactionInfo, TransactionMeta};
50
51mod recovered;
52pub use recovered::{Recovered, SignerRecoverable};
53
54#[cfg(feature = "serde")]
55pub use legacy::signed_legacy_serde;
56
57/// Bincode-compatible serde implementations for transaction types.
58#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
59pub mod serde_bincode_compat {
60    pub use super::{
61        eip1559::serde_bincode_compat::*, eip2930::serde_bincode_compat::*,
62        eip7702::serde_bincode_compat::*, legacy::serde_bincode_compat::*,
63    };
64}
65
66use alloy_eips::Typed2718;
67
68/// Represents a minimal EVM transaction.
69#[doc(alias = "Tx")]
70#[auto_impl::auto_impl(&, Arc)]
71pub trait Transaction: Typed2718 + fmt::Debug + any::Any + Send + Sync + 'static {
72    /// Get `chain_id`.
73    fn chain_id(&self) -> Option<ChainId>;
74
75    /// Get `nonce`.
76    fn nonce(&self) -> u64;
77
78    /// Get `gas_limit`.
79    fn gas_limit(&self) -> u64;
80
81    /// Get `gas_price`.
82    fn gas_price(&self) -> Option<u128>;
83
84    /// Returns the EIP-1559 the maximum fee per gas the caller is willing to pay.
85    ///
86    /// For legacy transactions this is `gas_price`.
87    ///
88    /// This is also commonly referred to as the "Gas Fee Cap".
89    fn max_fee_per_gas(&self) -> u128;
90
91    /// Returns the EIP-1559 Priority fee the caller is paying to the block author.
92    ///
93    /// This will return `None` for non-EIP1559 transactions
94    fn max_priority_fee_per_gas(&self) -> Option<u128>;
95
96    /// Max fee per blob gas for EIP-4844 transaction.
97    ///
98    /// Returns `None` for non-eip4844 transactions.
99    ///
100    /// This is also commonly referred to as the "Blob Gas Fee Cap".
101    fn max_fee_per_blob_gas(&self) -> Option<u128>;
102
103    /// Return the max priority fee per gas if the transaction is an EIP-1559 transaction, and
104    /// otherwise return the gas price.
105    ///
106    /// # Warning
107    ///
108    /// This is different than the `max_priority_fee_per_gas` method, which returns `None` for
109    /// non-EIP-1559 transactions.
110    fn priority_fee_or_price(&self) -> u128;
111
112    /// Returns the effective gas price for the given base fee.
113    ///
114    /// If the transaction is a legacy or EIP2930 transaction, the gas price is returned.
115    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128;
116
117    /// Returns the effective tip for this transaction.
118    ///
119    /// For EIP-1559 transactions: `min(max_fee_per_gas - base_fee, max_priority_fee_per_gas)`.
120    /// For legacy transactions: `gas_price - base_fee`.
121    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
122        let base_fee = base_fee as u128;
123
124        let max_fee_per_gas = self.max_fee_per_gas();
125
126        // Check if max_fee_per_gas is less than base_fee
127        if max_fee_per_gas < base_fee {
128            return None;
129        }
130
131        // Calculate the difference between max_fee_per_gas and base_fee
132        let fee = max_fee_per_gas - base_fee;
133
134        // Compare the fee with max_priority_fee_per_gas (or gas price for non-EIP1559 transactions)
135        self.max_priority_fee_per_gas()
136            .map_or(Some(fee), |priority_fee| Some(fee.min(priority_fee)))
137    }
138
139    /// Returns `true` if the transaction supports dynamic fees.
140    fn is_dynamic_fee(&self) -> bool;
141
142    /// Returns the transaction kind.
143    fn kind(&self) -> TxKind;
144
145    /// Returns true if the transaction is a contract creation.
146    /// We don't provide a default implementation via `kind` as it copies the 21-byte
147    /// [`TxKind`] for this simple check. A proper implementation shouldn't allocate.
148    fn is_create(&self) -> bool;
149
150    /// Get the transaction's address of the contract that will be called, or the address that will
151    /// receive the transfer.
152    ///
153    /// Returns `None` if this is a `CREATE` transaction.
154    fn to(&self) -> Option<Address> {
155        self.kind().to().copied()
156    }
157
158    /// Get `value`.
159    fn value(&self) -> U256;
160
161    /// Get `data`.
162    fn input(&self) -> &Bytes;
163
164    /// Returns the first 4bytes of the calldata for a function call.
165    ///
166    /// The selector specifies the function to be called.
167    fn function_selector(&self) -> Option<&Selector> {
168        if self.kind().is_call() {
169            self.input().get(..4).and_then(|s| TryFrom::try_from(s).ok())
170        } else {
171            None
172        }
173    }
174
175    /// Returns the EIP-2930 `access_list` for the particular transaction type. Returns `None` for
176    /// older transaction types.
177    fn access_list(&self) -> Option<&AccessList>;
178
179    /// Blob versioned hashes for eip4844 transaction. For previous transaction types this is
180    /// `None`.
181    fn blob_versioned_hashes(&self) -> Option<&[B256]>;
182
183    /// Returns the number of blobs of this transaction.
184    ///
185    /// This is convenience function for `len(blob_versioned_hashes)`.
186    ///
187    /// Returns `None` for non-eip4844 transactions.
188    fn blob_count(&self) -> Option<u64> {
189        self.blob_versioned_hashes().map(|h| h.len() as u64)
190    }
191
192    /// Returns the total gas for all blobs in this transaction.
193    ///
194    /// Returns `None` for non-eip4844 transactions.
195    #[inline]
196    fn blob_gas_used(&self) -> Option<u64> {
197        // SAFETY: we don't expect u64::MAX / DATA_GAS_PER_BLOB hashes in a single transaction
198        self.blob_count().map(|blobs| blobs * DATA_GAS_PER_BLOB)
199    }
200
201    /// Returns the [`SignedAuthorization`] list of the transaction.
202    ///
203    /// Returns `None` if this transaction is not EIP-7702.
204    fn authorization_list(&self) -> Option<&[SignedAuthorization]>;
205
206    /// Returns the number of blobs of [`SignedAuthorization`] in this transactions
207    ///
208    /// This is convenience function for `len(authorization_list)`.
209    ///
210    /// Returns `None` for non-eip7702 transactions.
211    fn authorization_count(&self) -> Option<u64> {
212        self.authorization_list().map(|auths| auths.len() as u64)
213    }
214}
215
216/// A signable transaction.
217///
218/// A transaction can have multiple signature types. This is usually
219/// [`alloy_primitives::PrimitiveSignature`], however, it may be different for future EIP-2718
220/// transaction types, or in other networks. For example, in Optimism, the deposit transaction
221/// signature is the unit type `()`.
222#[doc(alias = "SignableTx", alias = "TxSignable")]
223pub trait SignableTransaction<Signature>: Transaction {
224    /// Sets `chain_id`.
225    ///
226    /// Prefer [`set_chain_id_checked`](Self::set_chain_id_checked).
227    fn set_chain_id(&mut self, chain_id: ChainId);
228
229    /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the
230    /// existing `chain_id` if it is already set, returning `false` if they do not match.
231    fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
232        match self.chain_id() {
233            Some(tx_chain_id) => {
234                if tx_chain_id != chain_id {
235                    return false;
236                }
237                self.set_chain_id(chain_id);
238            }
239            None => {
240                self.set_chain_id(chain_id);
241            }
242        }
243        true
244    }
245
246    /// RLP-encodes the transaction for signing.
247    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
248
249    /// Outputs the length of the signature RLP encoding for the transaction.
250    fn payload_len_for_signature(&self) -> usize;
251
252    /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`.
253    ///
254    /// See [`SignableTransaction::encode_for_signing`].
255    fn encoded_for_signing(&self) -> Vec<u8> {
256        let mut buf = Vec::with_capacity(self.payload_len_for_signature());
257        self.encode_for_signing(&mut buf);
258        buf
259    }
260
261    /// Calculate the signing hash for the transaction.
262    fn signature_hash(&self) -> B256 {
263        keccak256(self.encoded_for_signing())
264    }
265
266    /// Convert to a [`Signed`] object.
267    fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
268    where
269        Self: Sized,
270    {
271        Signed::new_unhashed(self, signature)
272    }
273}
274
275// TODO: Remove in favor of dyn trait upcasting (TBD, see https://github.com/rust-lang/rust/issues/65991#issuecomment-1903120162)
276#[doc(hidden)]
277impl<S: 'static> dyn SignableTransaction<S> {
278    pub fn __downcast_ref<T: any::Any>(&self) -> Option<&T> {
279        if any::Any::type_id(self) == any::TypeId::of::<T>() {
280            unsafe { Some(&*(self as *const _ as *const T)) }
281        } else {
282            None
283        }
284    }
285}
286
287#[cfg(feature = "serde")]
288impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
289    #[inline]
290    fn chain_id(&self) -> Option<ChainId> {
291        self.inner.chain_id()
292    }
293
294    #[inline]
295    fn nonce(&self) -> u64 {
296        self.inner.nonce()
297    }
298
299    #[inline]
300    fn gas_limit(&self) -> u64 {
301        self.inner.gas_limit()
302    }
303
304    #[inline]
305    fn gas_price(&self) -> Option<u128> {
306        self.inner.gas_price()
307    }
308
309    #[inline]
310    fn max_fee_per_gas(&self) -> u128 {
311        self.inner.max_fee_per_gas()
312    }
313
314    #[inline]
315    fn max_priority_fee_per_gas(&self) -> Option<u128> {
316        self.inner.max_priority_fee_per_gas()
317    }
318
319    #[inline]
320    fn max_fee_per_blob_gas(&self) -> Option<u128> {
321        self.inner.max_fee_per_blob_gas()
322    }
323
324    #[inline]
325    fn priority_fee_or_price(&self) -> u128 {
326        self.inner.priority_fee_or_price()
327    }
328
329    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
330        self.inner.effective_gas_price(base_fee)
331    }
332
333    #[inline]
334    fn is_dynamic_fee(&self) -> bool {
335        self.inner.is_dynamic_fee()
336    }
337
338    #[inline]
339    fn kind(&self) -> TxKind {
340        self.inner.kind()
341    }
342
343    #[inline]
344    fn is_create(&self) -> bool {
345        self.inner.is_create()
346    }
347
348    #[inline]
349    fn value(&self) -> U256 {
350        self.inner.value()
351    }
352
353    #[inline]
354    fn input(&self) -> &Bytes {
355        self.inner.input()
356    }
357
358    #[inline]
359    fn access_list(&self) -> Option<&AccessList> {
360        self.inner.access_list()
361    }
362
363    #[inline]
364    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
365        self.inner.blob_versioned_hashes()
366    }
367
368    #[inline]
369    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
370        self.inner.authorization_list()
371    }
372}
373
374impl<L, R> Transaction for either::Either<L, R>
375where
376    L: Transaction,
377    R: Transaction,
378{
379    fn chain_id(&self) -> Option<ChainId> {
380        match self {
381            Self::Left(tx) => tx.chain_id(),
382            Self::Right(tx) => tx.chain_id(),
383        }
384    }
385
386    fn nonce(&self) -> u64 {
387        match self {
388            Self::Left(tx) => tx.nonce(),
389            Self::Right(tx) => tx.nonce(),
390        }
391    }
392
393    fn gas_limit(&self) -> u64 {
394        match self {
395            Self::Left(tx) => tx.gas_limit(),
396            Self::Right(tx) => tx.gas_limit(),
397        }
398    }
399
400    fn gas_price(&self) -> Option<u128> {
401        match self {
402            Self::Left(tx) => tx.gas_price(),
403            Self::Right(tx) => tx.gas_price(),
404        }
405    }
406
407    fn max_fee_per_gas(&self) -> u128 {
408        match self {
409            Self::Left(tx) => tx.max_fee_per_gas(),
410            Self::Right(tx) => tx.max_fee_per_gas(),
411        }
412    }
413
414    fn max_priority_fee_per_gas(&self) -> Option<u128> {
415        match self {
416            Self::Left(tx) => tx.max_priority_fee_per_gas(),
417            Self::Right(tx) => tx.max_priority_fee_per_gas(),
418        }
419    }
420
421    fn max_fee_per_blob_gas(&self) -> Option<u128> {
422        match self {
423            Self::Left(tx) => tx.max_fee_per_blob_gas(),
424            Self::Right(tx) => tx.max_fee_per_blob_gas(),
425        }
426    }
427
428    fn priority_fee_or_price(&self) -> u128 {
429        match self {
430            Self::Left(tx) => tx.priority_fee_or_price(),
431            Self::Right(tx) => tx.priority_fee_or_price(),
432        }
433    }
434
435    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
436        match self {
437            Self::Left(tx) => tx.effective_gas_price(base_fee),
438            Self::Right(tx) => tx.effective_gas_price(base_fee),
439        }
440    }
441
442    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
443        match self {
444            Self::Left(tx) => tx.effective_tip_per_gas(base_fee),
445            Self::Right(tx) => tx.effective_tip_per_gas(base_fee),
446        }
447    }
448
449    fn is_dynamic_fee(&self) -> bool {
450        match self {
451            Self::Left(tx) => tx.is_dynamic_fee(),
452            Self::Right(tx) => tx.is_dynamic_fee(),
453        }
454    }
455
456    fn kind(&self) -> TxKind {
457        match self {
458            Self::Left(tx) => tx.kind(),
459            Self::Right(tx) => tx.kind(),
460        }
461    }
462
463    fn is_create(&self) -> bool {
464        match self {
465            Self::Left(tx) => tx.is_create(),
466            Self::Right(tx) => tx.is_create(),
467        }
468    }
469
470    fn to(&self) -> Option<Address> {
471        match self {
472            Self::Left(tx) => tx.to(),
473            Self::Right(tx) => tx.to(),
474        }
475    }
476
477    fn value(&self) -> U256 {
478        match self {
479            Self::Left(tx) => tx.value(),
480            Self::Right(tx) => tx.value(),
481        }
482    }
483
484    fn input(&self) -> &Bytes {
485        match self {
486            Self::Left(tx) => tx.input(),
487            Self::Right(tx) => tx.input(),
488        }
489    }
490
491    fn function_selector(&self) -> Option<&Selector> {
492        match self {
493            Self::Left(tx) => tx.function_selector(),
494            Self::Right(tx) => tx.function_selector(),
495        }
496    }
497
498    fn access_list(&self) -> Option<&AccessList> {
499        match self {
500            Self::Left(tx) => tx.access_list(),
501            Self::Right(tx) => tx.access_list(),
502        }
503    }
504
505    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
506        match self {
507            Self::Left(tx) => tx.blob_versioned_hashes(),
508            Self::Right(tx) => tx.blob_versioned_hashes(),
509        }
510    }
511
512    fn blob_count(&self) -> Option<u64> {
513        match self {
514            Self::Left(tx) => tx.blob_count(),
515            Self::Right(tx) => tx.blob_count(),
516        }
517    }
518
519    fn blob_gas_used(&self) -> Option<u64> {
520        match self {
521            Self::Left(tx) => tx.blob_gas_used(),
522            Self::Right(tx) => tx.blob_gas_used(),
523        }
524    }
525
526    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
527        match self {
528            Self::Left(tx) => tx.authorization_list(),
529            Self::Right(tx) => tx.authorization_list(),
530        }
531    }
532
533    fn authorization_count(&self) -> Option<u64> {
534        match self {
535            Self::Left(tx) => tx.authorization_count(),
536            Self::Right(tx) => tx.authorization_count(),
537        }
538    }
539}