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