alloy_consensus/transaction/
mod.rs

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