alloy_consensus/transaction/
typed.rs

1pub use crate::transaction::envelope::EthereumTypedTransaction;
2use crate::{
3    error::ValueError,
4    transaction::{
5        eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
6        RlpEcdsaEncodableTx,
7    },
8    EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy,
9    TxType,
10};
11use alloy_eips::Typed2718;
12use alloy_primitives::{bytes::BufMut, ChainId, Signature, TxHash};
13
14/// Basic typed transaction which can contain both [`TxEip4844`] and [`TxEip4844WithSidecar`].
15pub type TypedTransaction = EthereumTypedTransaction<TxEip4844Variant>;
16
17impl<Eip4844> From<TxLegacy> for EthereumTypedTransaction<Eip4844> {
18    fn from(tx: TxLegacy) -> Self {
19        Self::Legacy(tx)
20    }
21}
22
23impl<Eip4844> From<TxEip2930> for EthereumTypedTransaction<Eip4844> {
24    fn from(tx: TxEip2930) -> Self {
25        Self::Eip2930(tx)
26    }
27}
28
29impl<Eip4844> From<TxEip1559> for EthereumTypedTransaction<Eip4844> {
30    fn from(tx: TxEip1559) -> Self {
31        Self::Eip1559(tx)
32    }
33}
34
35impl<Eip4844: From<TxEip4844>> From<TxEip4844> for EthereumTypedTransaction<Eip4844> {
36    fn from(tx: TxEip4844) -> Self {
37        Self::Eip4844(tx.into())
38    }
39}
40
41impl<T, Eip4844: From<TxEip4844WithSidecar<T>>> From<TxEip4844WithSidecar<T>>
42    for EthereumTypedTransaction<Eip4844>
43{
44    fn from(tx: TxEip4844WithSidecar<T>) -> Self {
45        Self::Eip4844(tx.into())
46    }
47}
48
49impl<Sidecar, Eip4844: From<TxEip4844Variant<Sidecar>>> From<TxEip4844Variant<Sidecar>>
50    for EthereumTypedTransaction<Eip4844>
51{
52    fn from(tx: TxEip4844Variant<Sidecar>) -> Self {
53        Self::Eip4844(tx.into())
54    }
55}
56
57impl<Eip4844> From<TxEip7702> for EthereumTypedTransaction<Eip4844> {
58    fn from(tx: TxEip7702) -> Self {
59        Self::Eip7702(tx)
60    }
61}
62
63impl<Eip4844> From<EthereumTxEnvelope<Eip4844>> for EthereumTypedTransaction<Eip4844> {
64    fn from(envelope: EthereumTxEnvelope<Eip4844>) -> Self {
65        match envelope {
66            EthereumTxEnvelope::Legacy(tx) => Self::Legacy(tx.strip_signature()),
67            EthereumTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.strip_signature()),
68            EthereumTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.strip_signature()),
69            EthereumTxEnvelope::Eip4844(tx) => Self::Eip4844(tx.strip_signature()),
70            EthereumTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.strip_signature()),
71        }
72    }
73}
74
75impl<T> From<EthereumTypedTransaction<TxEip4844WithSidecar<T>>>
76    for EthereumTypedTransaction<TxEip4844>
77{
78    fn from(value: EthereumTypedTransaction<TxEip4844WithSidecar<T>>) -> Self {
79        value.map_eip4844(|eip4844| eip4844.into())
80    }
81}
82
83impl<T> From<EthereumTypedTransaction<TxEip4844Variant<T>>>
84    for EthereumTypedTransaction<TxEip4844>
85{
86    fn from(value: EthereumTypedTransaction<TxEip4844Variant<T>>) -> Self {
87        value.map_eip4844(|eip4844| eip4844.into())
88    }
89}
90
91impl<T> From<EthereumTypedTransaction<TxEip4844>>
92    for EthereumTypedTransaction<TxEip4844Variant<T>>
93{
94    fn from(value: EthereumTypedTransaction<TxEip4844>) -> Self {
95        value.map_eip4844(|eip4844| eip4844.into())
96    }
97}
98
99impl<Eip4844> EthereumTypedTransaction<Eip4844> {
100    /// Converts the EIP-4844 variant of this transaction with the given closure.
101    ///
102    /// This is intended to convert between the EIP-4844 variants, specifically for stripping away
103    /// non consensus data (blob sidecar data).
104    pub fn map_eip4844<U>(self, mut f: impl FnMut(Eip4844) -> U) -> EthereumTypedTransaction<U> {
105        match self {
106            Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx),
107            Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx),
108            Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx),
109            Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(f(tx)),
110            Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx),
111        }
112    }
113
114    /// Converts this typed transaction into a signed [`EthereumTxEnvelope`]
115    pub fn into_envelope(self, signature: Signature) -> EthereumTxEnvelope<Eip4844> {
116        match self {
117            Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx.into_signed(signature)),
118            Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx.into_signed(signature)),
119            Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx.into_signed(signature)),
120            Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(Signed::new_unhashed(tx, signature)),
121            Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx.into_signed(signature)),
122        }
123    }
124}
125
126impl<Eip4844: RlpEcdsaEncodableTx> EthereumTypedTransaction<Eip4844> {
127    /// Return the [`TxType`] of the inner txn.
128    #[doc(alias = "transaction_type")]
129    pub const fn tx_type(&self) -> TxType {
130        match self {
131            Self::Legacy(_) => TxType::Legacy,
132            Self::Eip2930(_) => TxType::Eip2930,
133            Self::Eip1559(_) => TxType::Eip1559,
134            Self::Eip4844(_) => TxType::Eip4844,
135            Self::Eip7702(_) => TxType::Eip7702,
136        }
137    }
138
139    /// Return the inner legacy transaction if it exists.
140    pub const fn legacy(&self) -> Option<&TxLegacy> {
141        match self {
142            Self::Legacy(tx) => Some(tx),
143            _ => None,
144        }
145    }
146
147    /// Return the inner EIP-2930 transaction if it exists.
148    pub const fn eip2930(&self) -> Option<&TxEip2930> {
149        match self {
150            Self::Eip2930(tx) => Some(tx),
151            _ => None,
152        }
153    }
154
155    /// Return the inner EIP-1559 transaction if it exists.
156    pub const fn eip1559(&self) -> Option<&TxEip1559> {
157        match self {
158            Self::Eip1559(tx) => Some(tx),
159            _ => None,
160        }
161    }
162
163    /// Return the inner EIP-7702 transaction if it exists.
164    pub const fn eip7702(&self) -> Option<&TxEip7702> {
165        match self {
166            Self::Eip7702(tx) => Some(tx),
167            _ => None,
168        }
169    }
170
171    /// Consumes the type and returns the [`TxLegacy`] if this transaction is of that type.
172    pub fn try_into_legacy(self) -> Result<TxLegacy, ValueError<Self>> {
173        match self {
174            Self::Legacy(tx) => Ok(tx),
175            _ => Err(ValueError::new(self, "Expected legacy transaction")),
176        }
177    }
178
179    /// Consumes the type and returns the [`TxEip2930`]if this transaction is of that type.
180    pub fn try_into_eip2930(self) -> Result<TxEip2930, ValueError<Self>> {
181        match self {
182            Self::Eip2930(tx) => Ok(tx),
183            _ => Err(ValueError::new(self, "Expected EIP-2930 transaction")),
184        }
185    }
186
187    /// Consumes the type and returns the EIP-4844 if this transaction is of that type.
188    pub fn try_into_eip4844(self) -> Result<Eip4844, ValueError<Self>> {
189        match self {
190            Self::Eip4844(tx) => Ok(tx),
191            _ => Err(ValueError::new(self, "Expected EIP-4844 transaction")),
192        }
193    }
194
195    /// Consumes the type and returns the EIP-4844 if this transaction is of that type.
196    pub fn try_into_eip7702(self) -> Result<TxEip7702, ValueError<Self>> {
197        match self {
198            Self::Eip7702(tx) => Ok(tx),
199            _ => Err(ValueError::new(self, "Expected EIP-7702 transaction")),
200        }
201    }
202
203    /// Calculate the transaction hash for the given signature.
204    pub fn tx_hash(&self, signature: &Signature) -> TxHash {
205        match self {
206            Self::Legacy(tx) => tx.tx_hash(signature),
207            Self::Eip2930(tx) => tx.tx_hash(signature),
208            Self::Eip1559(tx) => tx.tx_hash(signature),
209            Self::Eip4844(tx) => tx.tx_hash(signature),
210            Self::Eip7702(tx) => tx.tx_hash(signature),
211        }
212    }
213}
214
215impl<Eip4844: RlpEcdsaEncodableTx + Typed2718> RlpEcdsaEncodableTx
216    for EthereumTypedTransaction<Eip4844>
217{
218    fn rlp_encoded_fields_length(&self) -> usize {
219        match self {
220            Self::Legacy(tx) => tx.rlp_encoded_fields_length(),
221            Self::Eip2930(tx) => tx.rlp_encoded_fields_length(),
222            Self::Eip1559(tx) => tx.rlp_encoded_fields_length(),
223            Self::Eip4844(tx) => tx.rlp_encoded_fields_length(),
224            Self::Eip7702(tx) => tx.rlp_encoded_fields_length(),
225        }
226    }
227
228    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
229        match self {
230            Self::Legacy(tx) => tx.rlp_encode_fields(out),
231            Self::Eip2930(tx) => tx.rlp_encode_fields(out),
232            Self::Eip1559(tx) => tx.rlp_encode_fields(out),
233            Self::Eip4844(tx) => tx.rlp_encode_fields(out),
234            Self::Eip7702(tx) => tx.rlp_encode_fields(out),
235        }
236    }
237
238    fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
239        match self {
240            Self::Legacy(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
241            Self::Eip2930(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
242            Self::Eip1559(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
243            Self::Eip4844(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
244            Self::Eip7702(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
245        }
246    }
247
248    fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
249        match self {
250            Self::Legacy(tx) => tx.eip2718_encode(signature, out),
251            Self::Eip2930(tx) => tx.eip2718_encode(signature, out),
252            Self::Eip1559(tx) => tx.eip2718_encode(signature, out),
253            Self::Eip4844(tx) => tx.eip2718_encode(signature, out),
254            Self::Eip7702(tx) => tx.eip2718_encode(signature, out),
255        }
256    }
257
258    fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
259        match self {
260            Self::Legacy(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
261            Self::Eip2930(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
262            Self::Eip1559(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
263            Self::Eip4844(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
264            Self::Eip7702(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
265        }
266    }
267
268    fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
269        match self {
270            Self::Legacy(tx) => tx.network_encode(signature, out),
271            Self::Eip2930(tx) => tx.network_encode(signature, out),
272            Self::Eip1559(tx) => tx.network_encode(signature, out),
273            Self::Eip4844(tx) => tx.network_encode(signature, out),
274            Self::Eip7702(tx) => tx.network_encode(signature, out),
275        }
276    }
277
278    fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
279        match self {
280            Self::Legacy(tx) => tx.tx_hash_with_type(signature, tx.ty()),
281            Self::Eip2930(tx) => tx.tx_hash_with_type(signature, tx.ty()),
282            Self::Eip1559(tx) => tx.tx_hash_with_type(signature, tx.ty()),
283            Self::Eip4844(tx) => tx.tx_hash_with_type(signature, tx.ty()),
284            Self::Eip7702(tx) => tx.tx_hash_with_type(signature, tx.ty()),
285        }
286    }
287
288    fn tx_hash(&self, signature: &Signature) -> TxHash {
289        match self {
290            Self::Legacy(tx) => tx.tx_hash(signature),
291            Self::Eip2930(tx) => tx.tx_hash(signature),
292            Self::Eip1559(tx) => tx.tx_hash(signature),
293            Self::Eip4844(tx) => tx.tx_hash(signature),
294            Self::Eip7702(tx) => tx.tx_hash(signature),
295        }
296    }
297}
298
299impl<Eip4844: SignableTransaction<Signature>> SignableTransaction<Signature>
300    for EthereumTypedTransaction<Eip4844>
301{
302    fn set_chain_id(&mut self, chain_id: ChainId) {
303        match self {
304            Self::Legacy(tx) => tx.set_chain_id(chain_id),
305            Self::Eip2930(tx) => tx.set_chain_id(chain_id),
306            Self::Eip1559(tx) => tx.set_chain_id(chain_id),
307            Self::Eip4844(tx) => tx.set_chain_id(chain_id),
308            Self::Eip7702(tx) => tx.set_chain_id(chain_id),
309        }
310    }
311
312    fn encode_for_signing(&self, out: &mut dyn BufMut) {
313        match self {
314            Self::Legacy(tx) => tx.encode_for_signing(out),
315            Self::Eip2930(tx) => tx.encode_for_signing(out),
316            Self::Eip1559(tx) => tx.encode_for_signing(out),
317            Self::Eip4844(tx) => tx.encode_for_signing(out),
318            Self::Eip7702(tx) => tx.encode_for_signing(out),
319        }
320    }
321
322    fn payload_len_for_signature(&self) -> usize {
323        match self {
324            Self::Legacy(tx) => tx.payload_len_for_signature(),
325            Self::Eip2930(tx) => tx.payload_len_for_signature(),
326            Self::Eip1559(tx) => tx.payload_len_for_signature(),
327            Self::Eip4844(tx) => tx.payload_len_for_signature(),
328            Self::Eip7702(tx) => tx.payload_len_for_signature(),
329        }
330    }
331}
332
333#[cfg(feature = "serde")]
334impl<Eip4844, T: From<EthereumTypedTransaction<Eip4844>>> From<EthereumTypedTransaction<Eip4844>>
335    for alloy_serde::WithOtherFields<T>
336{
337    fn from(value: EthereumTypedTransaction<Eip4844>) -> Self {
338        Self::new(value.into())
339    }
340}
341
342#[cfg(feature = "serde")]
343impl<Eip4844, T> From<EthereumTxEnvelope<Eip4844>> for alloy_serde::WithOtherFields<T>
344where
345    T: From<EthereumTxEnvelope<Eip4844>>,
346{
347    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
348        Self::new(value.into())
349    }
350}
351
352/// Bincode-compatible [`EthereumTypedTransaction`] serde implementation.
353#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
354pub(crate) mod serde_bincode_compat {
355    use alloc::borrow::Cow;
356    use serde::{Deserialize, Deserializer, Serialize, Serializer};
357    use serde_with::{DeserializeAs, SerializeAs};
358
359    /// Bincode-compatible [`super::EthereumTypedTransaction`] serde implementation.
360    ///
361    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
362    /// ```rust
363    /// use alloy_consensus::{serde_bincode_compat, EthereumTypedTransaction};
364    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
365    /// use serde_with::serde_as;
366    ///
367    /// #[serde_as]
368    /// #[derive(Serialize, Deserialize)]
369    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
370    ///     #[serde_as(as = "serde_bincode_compat::EthereumTypedTransaction<'_, T>")]
371    ///     receipt: EthereumTypedTransaction<T>,
372    /// }
373    /// ```
374    #[derive(Debug, Serialize, Deserialize)]
375    pub enum EthereumTypedTransaction<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
376        /// Legacy transaction
377        Legacy(crate::serde_bincode_compat::transaction::TxLegacy<'a>),
378        /// EIP-2930 transaction
379        Eip2930(crate::serde_bincode_compat::transaction::TxEip2930<'a>),
380        /// EIP-1559 transaction
381        Eip1559(crate::serde_bincode_compat::transaction::TxEip1559<'a>),
382        /// EIP-4844 transaction
383        /// Note: assumes EIP4844 is bincode compatible, which it is because no flatten or skipped
384        /// fields.
385        Eip4844(Cow<'a, Eip4844>),
386        /// EIP-7702 transaction
387        Eip7702(crate::serde_bincode_compat::transaction::TxEip7702<'a>),
388    }
389
390    impl<'a, T: Clone> From<&'a super::EthereumTypedTransaction<T>>
391        for EthereumTypedTransaction<'a, T>
392    {
393        fn from(value: &'a super::EthereumTypedTransaction<T>) -> Self {
394            match value {
395                super::EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
396                super::EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
397                super::EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
398                super::EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
399                super::EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
400            }
401        }
402    }
403
404    impl<'a, T: Clone> From<EthereumTypedTransaction<'a, T>> for super::EthereumTypedTransaction<T> {
405        fn from(value: EthereumTypedTransaction<'a, T>) -> Self {
406            match value {
407                EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
408                EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
409                EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
410                EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
411                EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
412            }
413        }
414    }
415
416    impl<T: Serialize + Clone> SerializeAs<super::EthereumTypedTransaction<T>>
417        for EthereumTypedTransaction<'_, T>
418    {
419        fn serialize_as<S>(
420            source: &super::EthereumTypedTransaction<T>,
421            serializer: S,
422        ) -> Result<S::Ok, S::Error>
423        where
424            S: Serializer,
425        {
426            EthereumTypedTransaction::<'_, T>::from(source).serialize(serializer)
427        }
428    }
429
430    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTypedTransaction<T>>
431        for EthereumTypedTransaction<'de, T>
432    {
433        fn deserialize_as<D>(
434            deserializer: D,
435        ) -> Result<super::EthereumTypedTransaction<T>, D::Error>
436        where
437            D: Deserializer<'de>,
438        {
439            EthereumTypedTransaction::<'_, T>::deserialize(deserializer).map(Into::into)
440        }
441    }
442
443    #[cfg(test)]
444    mod tests {
445        use super::super::{serde_bincode_compat, EthereumTypedTransaction};
446        use crate::TxEip4844;
447        use arbitrary::Arbitrary;
448        use bincode::config;
449        use rand::Rng;
450        use serde::{Deserialize, Serialize};
451        use serde_with::serde_as;
452
453        #[test]
454        fn test_typed_tx_bincode_roundtrip() {
455            #[serde_as]
456            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
457            struct Data {
458                #[serde_as(as = "serde_bincode_compat::EthereumTypedTransaction<'_>")]
459                transaction: EthereumTypedTransaction<TxEip4844>,
460            }
461
462            let mut bytes = [0u8; 1024];
463            rand::thread_rng().fill(bytes.as_mut_slice());
464            let data = Data {
465                transaction: EthereumTypedTransaction::arbitrary(
466                    &mut arbitrary::Unstructured::new(&bytes),
467                )
468                .unwrap(),
469            };
470
471            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
472            let (decoded, _) =
473                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
474            assert_eq!(decoded, data);
475        }
476    }
477}