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
63mod hashable;
64pub use hashable::TxHashable;
65
66#[cfg(feature = "serde")]
67pub use legacy::{signed_legacy_serde, untagged_legacy_serde};
68
69/// Bincode-compatible serde implementations for transaction types.
70#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
71pub mod serde_bincode_compat {
72    pub use super::{
73        eip1559::serde_bincode_compat::*, eip2930::serde_bincode_compat::*,
74        eip7702::serde_bincode_compat::*, envelope::serde_bincode_compat::*,
75        legacy::serde_bincode_compat::*, typed::serde_bincode_compat::*,
76    };
77}
78
79use alloy_eips::Typed2718;
80
81/// Represents a minimal EVM transaction.
82/// Currently, EIP-1559, EIP-4844, and EIP-7702 support dynamic fees.
83/// We call these transactions "dynamic fee transactions".
84/// We call non dynamic fee transactions(EIP-155, EIP-2930) "legacy fee transactions".
85#[doc(alias = "Tx")]
86#[auto_impl::auto_impl(&, Arc)]
87pub trait Transaction: Typed2718 + fmt::Debug + any::Any + Send + Sync + 'static {
88    /// Get `chain_id`.
89    fn chain_id(&self) -> Option<ChainId>;
90
91    /// Get `nonce`.
92    fn nonce(&self) -> u64;
93
94    /// Get `gas_limit`.
95    fn gas_limit(&self) -> u64;
96
97    /// Get `gas_price`.
98    fn gas_price(&self) -> Option<u128>;
99
100    /// For dynamic fee transactions returns the maximum fee per gas the caller is willing to pay.
101    ///
102    /// For legacy fee transactions this is `gas_price`.
103    ///
104    /// This is also commonly referred to as the "Gas Fee Cap".
105    fn max_fee_per_gas(&self) -> u128;
106
107    /// For dynamic fee transactions returns the Priority fee the caller is paying to the block
108    /// author.
109    ///
110    /// This will return `None` for legacy fee transactions
111    fn max_priority_fee_per_gas(&self) -> Option<u128>;
112
113    /// Max fee per blob gas for EIP-4844 transaction.
114    ///
115    /// Returns `None` for non-eip4844 transactions.
116    ///
117    /// This is also commonly referred to as the "Blob Gas Fee Cap".
118    fn max_fee_per_blob_gas(&self) -> Option<u128>;
119
120    /// Return the max priority fee per gas if the transaction is a dynamic fee transaction, and
121    /// otherwise return the gas price.
122    ///
123    /// # Warning
124    ///
125    /// This is different than the `max_priority_fee_per_gas` method, which returns `None` for
126    /// legacy fee transactions.
127    fn priority_fee_or_price(&self) -> u128;
128
129    /// Returns the effective gas price for the given base fee.
130    ///
131    /// If the transaction is a legacy fee transaction, the gas price is returned.
132    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128;
133
134    /// Returns the effective tip for this transaction.
135    ///
136    /// For dynamic fee transactions: `min(max_fee_per_gas - base_fee, max_priority_fee_per_gas)`.
137    /// For legacy fee transactions: `gas_price - base_fee`.
138    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
139        let base_fee = base_fee as u128;
140
141        let max_fee_per_gas = self.max_fee_per_gas();
142
143        // Check if max_fee_per_gas is less than base_fee
144        if max_fee_per_gas < base_fee {
145            return None;
146        }
147
148        // Calculate the difference between max_fee_per_gas and base_fee
149        let fee = max_fee_per_gas - base_fee;
150
151        // Compare the fee with max_priority_fee_per_gas (or gas price for legacy fee transactions)
152        self.max_priority_fee_per_gas()
153            .map_or(Some(fee), |priority_fee| Some(fee.min(priority_fee)))
154    }
155
156    /// Returns `true` if the transaction supports dynamic fees.
157    fn is_dynamic_fee(&self) -> bool;
158
159    /// Returns the transaction kind.
160    fn kind(&self) -> TxKind;
161
162    /// Returns true if the transaction is a contract creation.
163    /// We don't provide a default implementation via `kind` as it copies the 21-byte
164    /// [`TxKind`] for this simple check. A proper implementation shouldn't allocate.
165    fn is_create(&self) -> bool;
166
167    /// Get the transaction's address of the contract that will be called, or the address that will
168    /// receive the transfer.
169    ///
170    /// Returns `None` if this is a `CREATE` transaction.
171    fn to(&self) -> Option<Address> {
172        self.kind().into_to()
173    }
174
175    /// Get `value`.
176    fn value(&self) -> U256;
177
178    /// Get `data`.
179    fn input(&self) -> &Bytes;
180
181    /// Returns the first 4bytes of the calldata for a function call.
182    ///
183    /// The selector specifies the function to be called.
184    fn function_selector(&self) -> Option<&Selector> {
185        if self.kind().is_call() {
186            self.input().get(..4).and_then(|s| TryFrom::try_from(s).ok())
187        } else {
188            None
189        }
190    }
191
192    /// Returns the EIP-2930 `access_list` for the particular transaction type. Returns `None` for
193    /// older transaction types.
194    fn access_list(&self) -> Option<&AccessList>;
195
196    /// Blob versioned hashes for eip4844 transaction. For previous transaction types this is
197    /// `None`.
198    fn blob_versioned_hashes(&self) -> Option<&[B256]>;
199
200    /// Returns the number of blobs of this transaction.
201    ///
202    /// This is convenience function for `len(blob_versioned_hashes)`.
203    ///
204    /// Returns `None` for non-eip4844 transactions.
205    fn blob_count(&self) -> Option<u64> {
206        self.blob_versioned_hashes().map(|h| h.len() as u64)
207    }
208
209    /// Returns the total gas for all blobs in this transaction.
210    ///
211    /// Returns `None` for non-eip4844 transactions.
212    #[inline]
213    fn blob_gas_used(&self) -> Option<u64> {
214        // SAFETY: we don't expect u64::MAX / DATA_GAS_PER_BLOB hashes in a single transaction
215        self.blob_count().map(|blobs| blobs * DATA_GAS_PER_BLOB)
216    }
217
218    /// Returns the [`SignedAuthorization`] list of the transaction.
219    ///
220    /// Returns `None` if this transaction is not EIP-7702.
221    fn authorization_list(&self) -> Option<&[SignedAuthorization]>;
222
223    /// Returns the number of blobs of [`SignedAuthorization`] in this transactions
224    ///
225    /// This is convenience function for `len(authorization_list)`.
226    ///
227    /// Returns `None` for non-eip7702 transactions.
228    fn authorization_count(&self) -> Option<u64> {
229        self.authorization_list().map(|auths| auths.len() as u64)
230    }
231}
232
233/// A typed transaction envelope.
234pub trait TransactionEnvelope: Transaction {
235    /// The enum of transaction types.
236    type TxType: Typed2718;
237}
238
239/// A signable transaction.
240///
241/// A transaction can have multiple signature types. This is usually
242/// [`alloy_primitives::Signature`], however, it may be different for future EIP-2718
243/// transaction types, or in other networks. For example, in Optimism, the deposit transaction
244/// signature is the unit type `()`.
245#[doc(alias = "SignableTx", alias = "TxSignable")]
246pub trait SignableTransaction<Signature>: Transaction {
247    /// Sets `chain_id`.
248    ///
249    /// Prefer [`set_chain_id_checked`](Self::set_chain_id_checked).
250    fn set_chain_id(&mut self, chain_id: ChainId);
251
252    /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the
253    /// existing `chain_id` if it is already set, returning `false` if they do not match.
254    fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
255        match self.chain_id() {
256            Some(tx_chain_id) => {
257                if tx_chain_id != chain_id {
258                    return false;
259                }
260                // Chain ID already matches, no need to set it again
261            }
262            None => {
263                self.set_chain_id(chain_id);
264            }
265        }
266        true
267    }
268
269    /// RLP-encodes the transaction for signing.
270    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
271
272    /// Outputs the length of the signature RLP encoding for the transaction.
273    fn payload_len_for_signature(&self) -> usize;
274
275    /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`.
276    ///
277    /// See [`SignableTransaction::encode_for_signing`].
278    fn encoded_for_signing(&self) -> Vec<u8> {
279        let mut buf = Vec::with_capacity(self.payload_len_for_signature());
280        self.encode_for_signing(&mut buf);
281        buf
282    }
283
284    /// Calculate the signing hash for the transaction.
285    fn signature_hash(&self) -> B256 {
286        keccak256(self.encoded_for_signing())
287    }
288
289    /// Convert to a [`Signed`] object.
290    fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
291    where
292        Self: Sized,
293    {
294        Signed::new_unhashed(self, signature)
295    }
296}
297
298#[cfg(feature = "serde")]
299impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
300    #[inline]
301    fn chain_id(&self) -> Option<ChainId> {
302        self.inner.chain_id()
303    }
304
305    #[inline]
306    fn nonce(&self) -> u64 {
307        self.inner.nonce()
308    }
309
310    #[inline]
311    fn gas_limit(&self) -> u64 {
312        self.inner.gas_limit()
313    }
314
315    #[inline]
316    fn gas_price(&self) -> Option<u128> {
317        self.inner.gas_price()
318    }
319
320    #[inline]
321    fn max_fee_per_gas(&self) -> u128 {
322        self.inner.max_fee_per_gas()
323    }
324
325    #[inline]
326    fn max_priority_fee_per_gas(&self) -> Option<u128> {
327        self.inner.max_priority_fee_per_gas()
328    }
329
330    #[inline]
331    fn max_fee_per_blob_gas(&self) -> Option<u128> {
332        self.inner.max_fee_per_blob_gas()
333    }
334
335    #[inline]
336    fn priority_fee_or_price(&self) -> u128 {
337        self.inner.priority_fee_or_price()
338    }
339
340    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
341        self.inner.effective_gas_price(base_fee)
342    }
343
344    #[inline]
345    fn is_dynamic_fee(&self) -> bool {
346        self.inner.is_dynamic_fee()
347    }
348
349    #[inline]
350    fn kind(&self) -> TxKind {
351        self.inner.kind()
352    }
353
354    #[inline]
355    fn is_create(&self) -> bool {
356        self.inner.is_create()
357    }
358
359    #[inline]
360    fn value(&self) -> U256 {
361        self.inner.value()
362    }
363
364    #[inline]
365    fn input(&self) -> &Bytes {
366        self.inner.input()
367    }
368
369    #[inline]
370    fn access_list(&self) -> Option<&AccessList> {
371        self.inner.access_list()
372    }
373
374    #[inline]
375    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
376        self.inner.blob_versioned_hashes()
377    }
378
379    #[inline]
380    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
381        self.inner.authorization_list()
382    }
383}
384
385impl<L, R> Transaction for either::Either<L, R>
386where
387    L: Transaction,
388    R: Transaction,
389{
390    fn chain_id(&self) -> Option<ChainId> {
391        match self {
392            Self::Left(tx) => tx.chain_id(),
393            Self::Right(tx) => tx.chain_id(),
394        }
395    }
396
397    fn nonce(&self) -> u64 {
398        match self {
399            Self::Left(tx) => tx.nonce(),
400            Self::Right(tx) => tx.nonce(),
401        }
402    }
403
404    fn gas_limit(&self) -> u64 {
405        match self {
406            Self::Left(tx) => tx.gas_limit(),
407            Self::Right(tx) => tx.gas_limit(),
408        }
409    }
410
411    fn gas_price(&self) -> Option<u128> {
412        match self {
413            Self::Left(tx) => tx.gas_price(),
414            Self::Right(tx) => tx.gas_price(),
415        }
416    }
417
418    fn max_fee_per_gas(&self) -> u128 {
419        match self {
420            Self::Left(tx) => tx.max_fee_per_gas(),
421            Self::Right(tx) => tx.max_fee_per_gas(),
422        }
423    }
424
425    fn max_priority_fee_per_gas(&self) -> Option<u128> {
426        match self {
427            Self::Left(tx) => tx.max_priority_fee_per_gas(),
428            Self::Right(tx) => tx.max_priority_fee_per_gas(),
429        }
430    }
431
432    fn max_fee_per_blob_gas(&self) -> Option<u128> {
433        match self {
434            Self::Left(tx) => tx.max_fee_per_blob_gas(),
435            Self::Right(tx) => tx.max_fee_per_blob_gas(),
436        }
437    }
438
439    fn priority_fee_or_price(&self) -> u128 {
440        match self {
441            Self::Left(tx) => tx.priority_fee_or_price(),
442            Self::Right(tx) => tx.priority_fee_or_price(),
443        }
444    }
445
446    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
447        match self {
448            Self::Left(tx) => tx.effective_gas_price(base_fee),
449            Self::Right(tx) => tx.effective_gas_price(base_fee),
450        }
451    }
452
453    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
454        match self {
455            Self::Left(tx) => tx.effective_tip_per_gas(base_fee),
456            Self::Right(tx) => tx.effective_tip_per_gas(base_fee),
457        }
458    }
459
460    fn is_dynamic_fee(&self) -> bool {
461        match self {
462            Self::Left(tx) => tx.is_dynamic_fee(),
463            Self::Right(tx) => tx.is_dynamic_fee(),
464        }
465    }
466
467    fn kind(&self) -> TxKind {
468        match self {
469            Self::Left(tx) => tx.kind(),
470            Self::Right(tx) => tx.kind(),
471        }
472    }
473
474    fn is_create(&self) -> bool {
475        match self {
476            Self::Left(tx) => tx.is_create(),
477            Self::Right(tx) => tx.is_create(),
478        }
479    }
480
481    fn to(&self) -> Option<Address> {
482        match self {
483            Self::Left(tx) => tx.to(),
484            Self::Right(tx) => tx.to(),
485        }
486    }
487
488    fn value(&self) -> U256 {
489        match self {
490            Self::Left(tx) => tx.value(),
491            Self::Right(tx) => tx.value(),
492        }
493    }
494
495    fn input(&self) -> &Bytes {
496        match self {
497            Self::Left(tx) => tx.input(),
498            Self::Right(tx) => tx.input(),
499        }
500    }
501
502    fn function_selector(&self) -> Option<&Selector> {
503        match self {
504            Self::Left(tx) => tx.function_selector(),
505            Self::Right(tx) => tx.function_selector(),
506        }
507    }
508
509    fn access_list(&self) -> Option<&AccessList> {
510        match self {
511            Self::Left(tx) => tx.access_list(),
512            Self::Right(tx) => tx.access_list(),
513        }
514    }
515
516    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
517        match self {
518            Self::Left(tx) => tx.blob_versioned_hashes(),
519            Self::Right(tx) => tx.blob_versioned_hashes(),
520        }
521    }
522
523    fn blob_count(&self) -> Option<u64> {
524        match self {
525            Self::Left(tx) => tx.blob_count(),
526            Self::Right(tx) => tx.blob_count(),
527        }
528    }
529
530    fn blob_gas_used(&self) -> Option<u64> {
531        match self {
532            Self::Left(tx) => tx.blob_gas_used(),
533            Self::Right(tx) => tx.blob_gas_used(),
534        }
535    }
536
537    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
538        match self {
539            Self::Left(tx) => tx.authorization_list(),
540            Self::Right(tx) => tx.authorization_list(),
541        }
542    }
543
544    fn authorization_count(&self) -> Option<u64> {
545        match self {
546            Self::Left(tx) => tx.authorization_count(),
547            Self::Right(tx) => tx.authorization_count(),
548        }
549    }
550}
551
552/// Trait for types that provide access to a transaction hash reference.
553///
554/// This trait is implemented by types that contain or can provide a reference to a
555/// transaction hash ([`TxHash`]). It provides a standard interface for accessing
556/// transaction hashes without requiring ownership.
557#[auto_impl(&, &mut, Box)]
558pub trait TxHashRef {
559    /// Returns a reference to the transaction hash.
560    ///
561    /// This assumes the implementing type already owns or has computed the transaction hash.
562    fn tx_hash(&self) -> &TxHash;
563}
564
565impl<T: TxHashRef> TxHashRef for Recovered<T> {
566    fn tx_hash(&self) -> &TxHash {
567        self.inner().tx_hash()
568    }
569}
570
571impl<T: TxHashRef> TxHashRef for alloy_eips::eip2718::WithEncoded<T> {
572    fn tx_hash(&self) -> &TxHash {
573        self.value().tx_hash()
574    }
575}
576
577#[cfg(all(test, feature = "serde"))]
578mod tests {
579    use crate::{Signed, TransactionEnvelope, TxEip1559, TxEnvelope, TxType};
580    use alloy_primitives::Signature;
581    use rand::Rng;
582
583    #[test]
584    fn test_custom_envelope() {
585        use serde::{Serialize, Serializer};
586        fn serialize_with<S: Serializer>(
587            tx: &Signed<TxEip1559>,
588            serializer: S,
589        ) -> Result<S::Ok, S::Error> {
590            #[derive(Serialize)]
591            struct WithExtra<'a> {
592                #[serde(flatten)]
593                inner: &'a Signed<TxEip1559>,
594                extra: &'a str,
595            }
596            WithExtra { inner: tx, extra: "extra" }.serialize(serializer)
597        }
598
599        #[derive(Debug, Clone, TransactionEnvelope)]
600        #[envelope(alloy_consensus = crate, tx_type_name = MyTxType)]
601        enum MyEnvelope {
602            #[envelope(flatten)]
603            Ethereum(TxEnvelope),
604            #[envelope(ty = 10)]
605            MyTx(Signed<TxEip1559>),
606            #[envelope(ty = 11)]
607            #[serde(serialize_with = "serialize_with")]
608            AnotherMyTx(Signed<TxEip1559>),
609        }
610
611        assert_eq!(u8::from(MyTxType::Ethereum(TxType::Eip1559)), 2);
612        assert_eq!(u8::from(MyTxType::MyTx), 10);
613        assert_eq!(MyTxType::try_from(2u8).unwrap(), MyTxType::Ethereum(TxType::Eip1559));
614        assert_eq!(MyTxType::try_from(10u8).unwrap(), MyTxType::MyTx);
615
616        let mut bytes = [0u8; 1024];
617        rand::thread_rng().fill(bytes.as_mut_slice());
618        let tx = Signed::new_unhashed(
619            TxEip1559 {
620                chain_id: 1,
621                gas_limit: 21000,
622                max_fee_per_gas: 1000,
623                max_priority_fee_per_gas: 1000,
624                ..Default::default()
625            },
626            Signature::new(Default::default(), Default::default(), Default::default()),
627        );
628
629        let my_tx = serde_json::to_string(&MyEnvelope::MyTx(tx.clone())).unwrap();
630        let another_my_tx = serde_json::to_string(&MyEnvelope::AnotherMyTx(tx)).unwrap();
631
632        assert_eq!(
633            my_tx,
634            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"}"#
635        );
636        assert_eq!(
637            another_my_tx,
638            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"}"#
639        );
640    }
641}