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, TxHash, TxKind, B256, U256};
7use auto_impl::auto_impl;
8use core::{any, fmt};
9
10mod eip1559;
11pub use eip1559::TxEip1559;
12
13mod eip2930;
14pub use eip2930::TxEip2930;
15
16mod eip7702;
17pub use eip7702::TxEip7702;
18
19mod envelope;
20#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
21pub use envelope::serde_bincode_compat as envelope_serde_bincode_compat;
22pub use envelope::{EthereumTxEnvelope, TxEnvelope, TxType};
23
24/// [EIP-4844] constants, helpers, and types.
25pub mod eip4844;
26pub use eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
27
28mod eip4844_sidecar;
29#[cfg(feature = "kzg")]
30pub use eip4844_sidecar::BlobTransactionValidationError;
31pub use eip4844_sidecar::TxEip4844Sidecar;
32
33// Re-export 4844 helpers.
34pub use alloy_eips::eip4844::{
35    builder::{SidecarBuilder, SidecarCoder, SimpleCoder},
36    utils as eip4844_utils, Blob, BlobTransactionSidecar, Bytes48,
37};
38
39pub mod pooled;
40pub use pooled::PooledTransaction;
41
42/// Re-export for convenience
43pub use either::Either;
44
45mod legacy;
46pub use legacy::{from_eip155_value, to_eip155_value, TxLegacy};
47
48mod rlp;
49#[doc(hidden)]
50pub use rlp::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, RlpEcdsaTx};
51
52mod typed;
53pub use typed::{EthereumTypedTransaction, TypedTransaction};
54
55mod tx_type;
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, untagged_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 a 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 typed transaction envelope.
231pub trait TransactionEnvelope: Transaction {
232    /// The enum of transaction types.
233    type TxType: Typed2718;
234}
235
236/// A signable transaction.
237///
238/// A transaction can have multiple signature types. This is usually
239/// [`alloy_primitives::Signature`], however, it may be different for future EIP-2718
240/// transaction types, or in other networks. For example, in Optimism, the deposit transaction
241/// signature is the unit type `()`.
242#[doc(alias = "SignableTx", alias = "TxSignable")]
243pub trait SignableTransaction<Signature>: Transaction {
244    /// Sets `chain_id`.
245    ///
246    /// Prefer [`set_chain_id_checked`](Self::set_chain_id_checked).
247    fn set_chain_id(&mut self, chain_id: ChainId);
248
249    /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the
250    /// existing `chain_id` if it is already set, returning `false` if they do not match.
251    fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
252        match self.chain_id() {
253            Some(tx_chain_id) => {
254                if tx_chain_id != chain_id {
255                    return false;
256                }
257                self.set_chain_id(chain_id);
258            }
259            None => {
260                self.set_chain_id(chain_id);
261            }
262        }
263        true
264    }
265
266    /// RLP-encodes the transaction for signing.
267    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
268
269    /// Outputs the length of the signature RLP encoding for the transaction.
270    fn payload_len_for_signature(&self) -> usize;
271
272    /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`.
273    ///
274    /// See [`SignableTransaction::encode_for_signing`].
275    fn encoded_for_signing(&self) -> Vec<u8> {
276        let mut buf = Vec::with_capacity(self.payload_len_for_signature());
277        self.encode_for_signing(&mut buf);
278        buf
279    }
280
281    /// Calculate the signing hash for the transaction.
282    fn signature_hash(&self) -> B256 {
283        keccak256(self.encoded_for_signing())
284    }
285
286    /// Convert to a [`Signed`] object.
287    fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
288    where
289        Self: Sized,
290    {
291        Signed::new_unhashed(self, signature)
292    }
293}
294
295// TODO(MSRV-1.86): Remove in favor of dyn trait upcasting
296#[doc(hidden)]
297impl<S: 'static> dyn SignableTransaction<S> {
298    pub fn __downcast_ref<T: any::Any>(&self) -> Option<&T> {
299        if any::Any::type_id(self) == any::TypeId::of::<T>() {
300            unsafe { Some(&*(self as *const _ as *const T)) }
301        } else {
302            None
303        }
304    }
305}
306
307#[cfg(feature = "serde")]
308impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
309    #[inline]
310    fn chain_id(&self) -> Option<ChainId> {
311        self.inner.chain_id()
312    }
313
314    #[inline]
315    fn nonce(&self) -> u64 {
316        self.inner.nonce()
317    }
318
319    #[inline]
320    fn gas_limit(&self) -> u64 {
321        self.inner.gas_limit()
322    }
323
324    #[inline]
325    fn gas_price(&self) -> Option<u128> {
326        self.inner.gas_price()
327    }
328
329    #[inline]
330    fn max_fee_per_gas(&self) -> u128 {
331        self.inner.max_fee_per_gas()
332    }
333
334    #[inline]
335    fn max_priority_fee_per_gas(&self) -> Option<u128> {
336        self.inner.max_priority_fee_per_gas()
337    }
338
339    #[inline]
340    fn max_fee_per_blob_gas(&self) -> Option<u128> {
341        self.inner.max_fee_per_blob_gas()
342    }
343
344    #[inline]
345    fn priority_fee_or_price(&self) -> u128 {
346        self.inner.priority_fee_or_price()
347    }
348
349    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
350        self.inner.effective_gas_price(base_fee)
351    }
352
353    #[inline]
354    fn is_dynamic_fee(&self) -> bool {
355        self.inner.is_dynamic_fee()
356    }
357
358    #[inline]
359    fn kind(&self) -> TxKind {
360        self.inner.kind()
361    }
362
363    #[inline]
364    fn is_create(&self) -> bool {
365        self.inner.is_create()
366    }
367
368    #[inline]
369    fn value(&self) -> U256 {
370        self.inner.value()
371    }
372
373    #[inline]
374    fn input(&self) -> &Bytes {
375        self.inner.input()
376    }
377
378    #[inline]
379    fn access_list(&self) -> Option<&AccessList> {
380        self.inner.access_list()
381    }
382
383    #[inline]
384    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
385        self.inner.blob_versioned_hashes()
386    }
387
388    #[inline]
389    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
390        self.inner.authorization_list()
391    }
392}
393
394impl<L, R> Transaction for either::Either<L, R>
395where
396    L: Transaction,
397    R: Transaction,
398{
399    fn chain_id(&self) -> Option<ChainId> {
400        match self {
401            Self::Left(tx) => tx.chain_id(),
402            Self::Right(tx) => tx.chain_id(),
403        }
404    }
405
406    fn nonce(&self) -> u64 {
407        match self {
408            Self::Left(tx) => tx.nonce(),
409            Self::Right(tx) => tx.nonce(),
410        }
411    }
412
413    fn gas_limit(&self) -> u64 {
414        match self {
415            Self::Left(tx) => tx.gas_limit(),
416            Self::Right(tx) => tx.gas_limit(),
417        }
418    }
419
420    fn gas_price(&self) -> Option<u128> {
421        match self {
422            Self::Left(tx) => tx.gas_price(),
423            Self::Right(tx) => tx.gas_price(),
424        }
425    }
426
427    fn max_fee_per_gas(&self) -> u128 {
428        match self {
429            Self::Left(tx) => tx.max_fee_per_gas(),
430            Self::Right(tx) => tx.max_fee_per_gas(),
431        }
432    }
433
434    fn max_priority_fee_per_gas(&self) -> Option<u128> {
435        match self {
436            Self::Left(tx) => tx.max_priority_fee_per_gas(),
437            Self::Right(tx) => tx.max_priority_fee_per_gas(),
438        }
439    }
440
441    fn max_fee_per_blob_gas(&self) -> Option<u128> {
442        match self {
443            Self::Left(tx) => tx.max_fee_per_blob_gas(),
444            Self::Right(tx) => tx.max_fee_per_blob_gas(),
445        }
446    }
447
448    fn priority_fee_or_price(&self) -> u128 {
449        match self {
450            Self::Left(tx) => tx.priority_fee_or_price(),
451            Self::Right(tx) => tx.priority_fee_or_price(),
452        }
453    }
454
455    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
456        match self {
457            Self::Left(tx) => tx.effective_gas_price(base_fee),
458            Self::Right(tx) => tx.effective_gas_price(base_fee),
459        }
460    }
461
462    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
463        match self {
464            Self::Left(tx) => tx.effective_tip_per_gas(base_fee),
465            Self::Right(tx) => tx.effective_tip_per_gas(base_fee),
466        }
467    }
468
469    fn is_dynamic_fee(&self) -> bool {
470        match self {
471            Self::Left(tx) => tx.is_dynamic_fee(),
472            Self::Right(tx) => tx.is_dynamic_fee(),
473        }
474    }
475
476    fn kind(&self) -> TxKind {
477        match self {
478            Self::Left(tx) => tx.kind(),
479            Self::Right(tx) => tx.kind(),
480        }
481    }
482
483    fn is_create(&self) -> bool {
484        match self {
485            Self::Left(tx) => tx.is_create(),
486            Self::Right(tx) => tx.is_create(),
487        }
488    }
489
490    fn to(&self) -> Option<Address> {
491        match self {
492            Self::Left(tx) => tx.to(),
493            Self::Right(tx) => tx.to(),
494        }
495    }
496
497    fn value(&self) -> U256 {
498        match self {
499            Self::Left(tx) => tx.value(),
500            Self::Right(tx) => tx.value(),
501        }
502    }
503
504    fn input(&self) -> &Bytes {
505        match self {
506            Self::Left(tx) => tx.input(),
507            Self::Right(tx) => tx.input(),
508        }
509    }
510
511    fn function_selector(&self) -> Option<&Selector> {
512        match self {
513            Self::Left(tx) => tx.function_selector(),
514            Self::Right(tx) => tx.function_selector(),
515        }
516    }
517
518    fn access_list(&self) -> Option<&AccessList> {
519        match self {
520            Self::Left(tx) => tx.access_list(),
521            Self::Right(tx) => tx.access_list(),
522        }
523    }
524
525    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
526        match self {
527            Self::Left(tx) => tx.blob_versioned_hashes(),
528            Self::Right(tx) => tx.blob_versioned_hashes(),
529        }
530    }
531
532    fn blob_count(&self) -> Option<u64> {
533        match self {
534            Self::Left(tx) => tx.blob_count(),
535            Self::Right(tx) => tx.blob_count(),
536        }
537    }
538
539    fn blob_gas_used(&self) -> Option<u64> {
540        match self {
541            Self::Left(tx) => tx.blob_gas_used(),
542            Self::Right(tx) => tx.blob_gas_used(),
543        }
544    }
545
546    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
547        match self {
548            Self::Left(tx) => tx.authorization_list(),
549            Self::Right(tx) => tx.authorization_list(),
550        }
551    }
552
553    fn authorization_count(&self) -> Option<u64> {
554        match self {
555            Self::Left(tx) => tx.authorization_count(),
556            Self::Right(tx) => tx.authorization_count(),
557        }
558    }
559}
560
561/// Trait for types that provide access to a transaction hash reference.
562///
563/// This trait is implemented by types that contain or can provide a reference to a
564/// transaction hash ([`TxHash`]). It provides a standard interface for accessing
565/// transaction hashes without requiring ownership.
566#[auto_impl(&, &mut, Box)]
567pub trait TxHashRef {
568    /// Returns a reference to the transaction hash.
569    ///
570    /// This assumes the implementing type already owns or has computed the transaction hash.
571    fn tx_hash(&self) -> &TxHash;
572}
573
574impl<T: TxHashRef> TxHashRef for Recovered<T> {
575    fn tx_hash(&self) -> &TxHash {
576        self.inner().tx_hash()
577    }
578}
579
580impl<T: TxHashRef> TxHashRef for alloy_eips::eip2718::WithEncoded<T> {
581    fn tx_hash(&self) -> &TxHash {
582        self.value().tx_hash()
583    }
584}
585
586#[cfg(test)]
587#[allow(unused_imports)]
588mod tests {
589    use crate::{Signed, TransactionEnvelope, TxEip1559, TxEnvelope, TxType};
590    use alloy_primitives::Signature;
591    use rand::Rng;
592
593    #[test]
594    #[cfg(feature = "serde")]
595    fn test_custom_envelope() {
596        use serde::{Serialize, Serializer};
597        fn serialize_with<S: Serializer>(
598            tx: &Signed<TxEip1559>,
599            serializer: S,
600        ) -> Result<S::Ok, S::Error> {
601            #[derive(Serialize)]
602            struct WithExtra<'a> {
603                #[serde(flatten)]
604                inner: &'a Signed<TxEip1559>,
605                extra: &'a str,
606            }
607            WithExtra { inner: tx, extra: "extra" }.serialize(serializer)
608        }
609
610        #[derive(Debug, Clone, TransactionEnvelope)]
611        #[envelope(alloy_consensus = crate, tx_type_name = MyTxType)]
612        enum MyEnvelope {
613            #[envelope(flatten)]
614            Ethereum(TxEnvelope),
615            #[envelope(ty = 10)]
616            MyTx(Signed<TxEip1559>),
617            #[envelope(ty = 11)]
618            #[serde(serialize_with = "serialize_with")]
619            AnotherMyTx(Signed<TxEip1559>),
620        }
621
622        assert_eq!(u8::from(MyTxType::Ethereum(TxType::Eip1559)), 2);
623        assert_eq!(u8::from(MyTxType::MyTx), 10);
624        assert_eq!(MyTxType::try_from(2u8).unwrap(), MyTxType::Ethereum(TxType::Eip1559));
625        assert_eq!(MyTxType::try_from(10u8).unwrap(), MyTxType::MyTx);
626
627        let mut bytes = [0u8; 1024];
628        rand::thread_rng().fill(bytes.as_mut_slice());
629        let tx = Signed::new_unhashed(
630            TxEip1559 {
631                chain_id: 1,
632                gas_limit: 21000,
633                max_fee_per_gas: 1000,
634                max_priority_fee_per_gas: 1000,
635                ..Default::default()
636            },
637            Signature::new(Default::default(), Default::default(), Default::default()),
638        );
639
640        let my_tx = serde_json::to_string(&MyEnvelope::MyTx(tx.clone())).unwrap();
641        let another_my_tx = serde_json::to_string(&MyEnvelope::AnotherMyTx(tx)).unwrap();
642
643        assert_eq!(
644            my_tx,
645            r#"{"type":"0xa","chainId":"0x1","nonce":"0x0","gas":"0x5208","maxFeePerGas":"0x3e8","maxPriorityFeePerGas":"0x3e8","to":null,"value":"0x0","accessList":[],"input":"0x","r":"0x0","s":"0x0","yParity":"0x0","v":"0x0","hash":"0x2eaaca5609601faae806f5147abb8f51ae91cba12604bedc23a16f2776d5a97b"}"#
646        );
647        assert_eq!(
648            another_my_tx,
649            r#"{"type":"0xb","chainId":"0x1","nonce":"0x0","gas":"0x5208","maxFeePerGas":"0x3e8","maxPriorityFeePerGas":"0x3e8","to":null,"value":"0x0","accessList":[],"input":"0x","r":"0x0","s":"0x0","yParity":"0x0","v":"0x0","hash":"0x2eaaca5609601faae806f5147abb8f51ae91cba12604bedc23a16f2776d5a97b","extra":"extra"}"#
650        );
651    }
652}