alloy_network/any/
either.rs

1use crate::{UnknownTxEnvelope, UnknownTypedTransaction};
2use alloy_consensus::{
3    error::ValueError, transaction::Either, Signed, Transaction as TransactionTrait, TxEip1559,
4    TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, Typed2718, TypedTransaction,
5};
6use alloy_eips::{
7    eip2718::{Decodable2718, Encodable2718},
8    eip7702::SignedAuthorization,
9};
10use alloy_primitives::{Bytes, ChainId, B256, U256};
11use alloy_rpc_types_eth::{AccessList, TransactionRequest};
12use alloy_serde::WithOtherFields;
13
14/// Unsigned transaction type for a catch-all network.
15#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[serde(untagged)]
17#[doc(alias = "AnyTypedTx")]
18pub enum AnyTypedTransaction {
19    /// An Ethereum transaction.
20    Ethereum(TypedTransaction),
21    /// A transaction with unknown type.
22    Unknown(UnknownTypedTransaction),
23}
24
25impl From<UnknownTypedTransaction> for AnyTypedTransaction {
26    fn from(value: UnknownTypedTransaction) -> Self {
27        Self::Unknown(value)
28    }
29}
30
31impl From<TypedTransaction> for AnyTypedTransaction {
32    fn from(value: TypedTransaction) -> Self {
33        Self::Ethereum(value)
34    }
35}
36
37impl From<AnyTxEnvelope> for AnyTypedTransaction {
38    fn from(value: AnyTxEnvelope) -> Self {
39        match value {
40            AnyTxEnvelope::Ethereum(tx) => Self::Ethereum(tx.into()),
41            AnyTxEnvelope::Unknown(UnknownTxEnvelope { inner, .. }) => inner.into(),
42        }
43    }
44}
45
46impl From<AnyTypedTransaction> for WithOtherFields<TransactionRequest> {
47    fn from(value: AnyTypedTransaction) -> Self {
48        match value {
49            AnyTypedTransaction::Ethereum(tx) => Self::new(tx.into()),
50            AnyTypedTransaction::Unknown(UnknownTypedTransaction { ty, mut fields, .. }) => {
51                fields.insert("type".to_string(), serde_json::Value::Number(ty.0.into()));
52                Self { inner: Default::default(), other: fields }
53            }
54        }
55    }
56}
57
58impl From<AnyTxEnvelope> for WithOtherFields<TransactionRequest> {
59    fn from(value: AnyTxEnvelope) -> Self {
60        AnyTypedTransaction::from(value).into()
61    }
62}
63
64impl TransactionTrait for AnyTypedTransaction {
65    #[inline]
66    fn chain_id(&self) -> Option<ChainId> {
67        match self {
68            Self::Ethereum(inner) => inner.chain_id(),
69            Self::Unknown(inner) => inner.chain_id(),
70        }
71    }
72
73    #[inline]
74    fn nonce(&self) -> u64 {
75        match self {
76            Self::Ethereum(inner) => inner.nonce(),
77            Self::Unknown(inner) => inner.nonce(),
78        }
79    }
80
81    #[inline]
82    fn gas_limit(&self) -> u64 {
83        match self {
84            Self::Ethereum(inner) => inner.gas_limit(),
85            Self::Unknown(inner) => inner.gas_limit(),
86        }
87    }
88
89    #[inline]
90    fn gas_price(&self) -> Option<u128> {
91        match self {
92            Self::Ethereum(inner) => inner.gas_price(),
93            Self::Unknown(inner) => inner.gas_price(),
94        }
95    }
96
97    #[inline]
98    fn max_fee_per_gas(&self) -> u128 {
99        match self {
100            Self::Ethereum(inner) => inner.max_fee_per_gas(),
101            Self::Unknown(inner) => inner.max_fee_per_gas(),
102        }
103    }
104
105    #[inline]
106    fn max_priority_fee_per_gas(&self) -> Option<u128> {
107        match self {
108            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
109            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
110        }
111    }
112
113    #[inline]
114    fn max_fee_per_blob_gas(&self) -> Option<u128> {
115        match self {
116            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
117            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
118        }
119    }
120
121    #[inline]
122    fn priority_fee_or_price(&self) -> u128 {
123        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
124    }
125
126    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
127        match self {
128            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
129            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
130        }
131    }
132
133    #[inline]
134    fn is_dynamic_fee(&self) -> bool {
135        match self {
136            Self::Ethereum(inner) => inner.is_dynamic_fee(),
137            Self::Unknown(inner) => inner.is_dynamic_fee(),
138        }
139    }
140
141    fn kind(&self) -> alloy_primitives::TxKind {
142        match self {
143            Self::Ethereum(inner) => inner.kind(),
144            Self::Unknown(inner) => inner.kind(),
145        }
146    }
147
148    #[inline]
149    fn is_create(&self) -> bool {
150        match self {
151            Self::Ethereum(inner) => inner.is_create(),
152            Self::Unknown(inner) => inner.is_create(),
153        }
154    }
155
156    #[inline]
157    fn value(&self) -> U256 {
158        match self {
159            Self::Ethereum(inner) => inner.value(),
160            Self::Unknown(inner) => inner.value(),
161        }
162    }
163
164    #[inline]
165    fn input(&self) -> &Bytes {
166        match self {
167            Self::Ethereum(inner) => inner.input(),
168            Self::Unknown(inner) => inner.input(),
169        }
170    }
171
172    #[inline]
173    fn access_list(&self) -> Option<&AccessList> {
174        match self {
175            Self::Ethereum(inner) => inner.access_list(),
176            Self::Unknown(inner) => inner.access_list(),
177        }
178    }
179
180    #[inline]
181    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
182        match self {
183            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
184            Self::Unknown(inner) => inner.blob_versioned_hashes(),
185        }
186    }
187
188    #[inline]
189    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
190        match self {
191            Self::Ethereum(inner) => inner.authorization_list(),
192            Self::Unknown(inner) => inner.authorization_list(),
193        }
194    }
195}
196
197impl Typed2718 for AnyTypedTransaction {
198    fn ty(&self) -> u8 {
199        match self {
200            Self::Ethereum(inner) => inner.ty(),
201            Self::Unknown(inner) => inner.ty(),
202        }
203    }
204}
205
206/// Transaction envelope for a catch-all network.
207#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
208#[serde(untagged)]
209#[doc(alias = "AnyTransactionEnvelope")]
210pub enum AnyTxEnvelope {
211    /// An Ethereum transaction.
212    Ethereum(TxEnvelope),
213    /// A transaction with unknown type.
214    Unknown(UnknownTxEnvelope),
215}
216
217impl AnyTxEnvelope {
218    /// Returns true if this is the ethereum transaction variant
219    pub const fn is_ethereum(&self) -> bool {
220        matches!(self, Self::Ethereum(_))
221    }
222
223    /// Returns true if this is the unknown transaction variant
224    pub const fn is_unknown(&self) -> bool {
225        matches!(self, Self::Unknown(_))
226    }
227
228    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
229    pub const fn as_envelope(&self) -> Option<&TxEnvelope> {
230        match self {
231            Self::Ethereum(inner) => Some(inner),
232            Self::Unknown(_) => None,
233        }
234    }
235
236    /// Returns the inner [`UnknownTxEnvelope`] if it is an unknown variant.
237    pub const fn as_unknown(&self) -> Option<&UnknownTxEnvelope> {
238        match self {
239            Self::Unknown(inner) => Some(inner),
240            Self::Ethereum(_) => None,
241        }
242    }
243
244    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
245    /// If the transaction is not an Ethereum transaction, it is returned as an error.
246    pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<Self>> {
247        match self {
248            Self::Ethereum(inner) => Ok(inner),
249            this => Err(ValueError::new_static(this, "unknown transaction envelope")),
250        }
251    }
252
253    /// Returns the inner [`UnknownTxEnvelope`], if it is an unknown transaction.
254    /// If the transaction is not an unknown transaction, it is returned as an error.
255    pub fn try_into_unknown(self) -> Result<UnknownTxEnvelope, Self> {
256        match self {
257            Self::Unknown(inner) => Ok(inner),
258            this => Err(this),
259        }
260    }
261
262    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an unknown
263    /// variant.
264    ///
265    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
266    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
267    ///
268    /// # Examples
269    ///
270    /// ```no_run
271    /// # use alloy_network::any::AnyTxEnvelope;
272    /// # use alloy_consensus::transaction::Either;
273    /// # // Assuming you have a custom type: struct CustomTx;
274    /// # // impl TryFrom<alloy_network::any::UnknownTxEnvelope> for CustomTx { ... }
275    /// # fn example(envelope: AnyTxEnvelope) -> Result<(), Box<dyn std::error::Error>> {
276    /// # struct CustomTx;
277    /// # impl TryFrom<alloy_network::any::UnknownTxEnvelope> for CustomTx {
278    /// #     type Error = String;
279    /// #     fn try_from(_: alloy_network::any::UnknownTxEnvelope) -> Result<Self, Self::Error> {
280    /// #         Ok(CustomTx)
281    /// #     }
282    /// # }
283    /// match envelope.try_into_either::<CustomTx>()? {
284    ///     Either::Left(eth_tx) => { /* Ethereum transaction */ }
285    ///     Either::Right(custom_tx) => { /* Custom transaction */ }
286    /// }
287    /// # Ok(())
288    /// # }
289    /// ```
290    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
291    where
292        T: TryFrom<UnknownTxEnvelope>,
293    {
294        self.try_map_unknown(|inner| inner.try_into())
295    }
296
297    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an
298    /// [`AnyTxEnvelope::Unknown`].
299    ///
300    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
301    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
302    ///
303    /// Use this for custom conversion logic when [`try_into_either`](Self::try_into_either) is
304    /// insufficient.
305    ///
306    /// # Examples
307    ///
308    /// ```no_run
309    /// # use alloy_network::any::AnyTxEnvelope;
310    /// # use alloy_consensus::transaction::Either;
311    /// # use alloy_primitives::B256;
312    /// # fn example(envelope: AnyTxEnvelope) -> Result<(), Box<dyn std::error::Error>> {
313    /// let result = envelope.try_map_unknown(|unknown| Ok::<B256, String>(unknown.hash))?;
314    /// match result {
315    ///     Either::Left(eth_tx) => { /* Ethereum */ }
316    ///     Either::Right(hash) => { /* Unknown tx hash */ }
317    /// }
318    /// # Ok(())
319    /// # }
320    /// ```
321    pub fn try_map_unknown<T, E>(
322        self,
323        f: impl FnOnce(UnknownTxEnvelope) -> Result<T, E>,
324    ) -> Result<Either<TxEnvelope, T>, E> {
325        match self {
326            Self::Ethereum(tx) => Ok(Either::Left(tx)),
327            Self::Unknown(tx) => Ok(Either::Right(f(tx)?)),
328        }
329    }
330
331    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
332    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
333        match self.as_envelope() {
334            Some(TxEnvelope::Legacy(tx)) => Some(tx),
335            _ => None,
336        }
337    }
338
339    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
340    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
341        match self.as_envelope() {
342            Some(TxEnvelope::Eip2930(tx)) => Some(tx),
343            _ => None,
344        }
345    }
346
347    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
348    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
349        match self.as_envelope() {
350            Some(TxEnvelope::Eip1559(tx)) => Some(tx),
351            _ => None,
352        }
353    }
354
355    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
356    pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
357        match self.as_envelope() {
358            Some(TxEnvelope::Eip4844(tx)) => Some(tx),
359            _ => None,
360        }
361    }
362
363    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
364    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
365        match self.as_envelope() {
366            Some(TxEnvelope::Eip7702(tx)) => Some(tx),
367            _ => None,
368        }
369    }
370
371    /// Returns true if the transaction is a legacy transaction.
372    #[inline]
373    pub const fn is_legacy(&self) -> bool {
374        matches!(self.as_envelope(), Some(TxEnvelope::Legacy(_)))
375    }
376
377    /// Returns true if the transaction is an EIP-2930 transaction.
378    #[inline]
379    pub const fn is_eip2930(&self) -> bool {
380        matches!(self.as_envelope(), Some(TxEnvelope::Eip2930(_)))
381    }
382
383    /// Returns true if the transaction is an EIP-1559 transaction.
384    #[inline]
385    pub const fn is_eip1559(&self) -> bool {
386        matches!(self.as_envelope(), Some(TxEnvelope::Eip1559(_)))
387    }
388
389    /// Returns true if the transaction is an EIP-4844 transaction.
390    #[inline]
391    pub const fn is_eip4844(&self) -> bool {
392        matches!(self.as_envelope(), Some(TxEnvelope::Eip4844(_)))
393    }
394
395    /// Returns true if the transaction is an EIP-7702 transaction.
396    #[inline]
397    pub const fn is_eip7702(&self) -> bool {
398        matches!(self.as_envelope(), Some(TxEnvelope::Eip7702(_)))
399    }
400}
401
402impl Typed2718 for AnyTxEnvelope {
403    fn ty(&self) -> u8 {
404        match self {
405            Self::Ethereum(inner) => inner.ty(),
406            Self::Unknown(inner) => inner.ty(),
407        }
408    }
409}
410
411impl Encodable2718 for AnyTxEnvelope {
412    fn encode_2718_len(&self) -> usize {
413        match self {
414            Self::Ethereum(t) => t.encode_2718_len(),
415            Self::Unknown(_) => 1,
416        }
417    }
418
419    #[track_caller]
420    fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
421        match self {
422            Self::Ethereum(t) => t.encode_2718(out),
423            Self::Unknown(inner) => {
424                panic!(
425                    "Attempted to encode unknown transaction type: {}. This is not a bug in alloy. To encode or decode unknown transaction types, use a custom Transaction type and a custom Network implementation. See https://docs.rs/alloy-network/latest/alloy_network/ for network documentation.",
426                    inner.as_ref().ty
427                )
428            }
429        }
430    }
431
432    fn trie_hash(&self) -> B256 {
433        match self {
434            Self::Ethereum(tx) => tx.trie_hash(),
435            Self::Unknown(inner) => inner.hash,
436        }
437    }
438}
439
440impl Decodable2718 for AnyTxEnvelope {
441    fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
442        TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
443    }
444
445    fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
446        TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
447    }
448}
449
450impl TransactionTrait for AnyTxEnvelope {
451    #[inline]
452    fn chain_id(&self) -> Option<ChainId> {
453        match self {
454            Self::Ethereum(inner) => inner.chain_id(),
455            Self::Unknown(inner) => inner.chain_id(),
456        }
457    }
458
459    #[inline]
460    fn nonce(&self) -> u64 {
461        match self {
462            Self::Ethereum(inner) => inner.nonce(),
463            Self::Unknown(inner) => inner.nonce(),
464        }
465    }
466
467    #[inline]
468    fn gas_limit(&self) -> u64 {
469        match self {
470            Self::Ethereum(inner) => inner.gas_limit(),
471            Self::Unknown(inner) => inner.gas_limit(),
472        }
473    }
474
475    #[inline]
476    fn gas_price(&self) -> Option<u128> {
477        match self {
478            Self::Ethereum(inner) => inner.gas_price(),
479            Self::Unknown(inner) => inner.gas_price(),
480        }
481    }
482
483    #[inline]
484    fn max_fee_per_gas(&self) -> u128 {
485        match self {
486            Self::Ethereum(inner) => inner.max_fee_per_gas(),
487            Self::Unknown(inner) => inner.max_fee_per_gas(),
488        }
489    }
490
491    #[inline]
492    fn max_priority_fee_per_gas(&self) -> Option<u128> {
493        match self {
494            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
495            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
496        }
497    }
498
499    #[inline]
500    fn max_fee_per_blob_gas(&self) -> Option<u128> {
501        match self {
502            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
503            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
504        }
505    }
506
507    #[inline]
508    fn priority_fee_or_price(&self) -> u128 {
509        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
510    }
511
512    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
513        match self {
514            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
515            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
516        }
517    }
518
519    #[inline]
520    fn is_dynamic_fee(&self) -> bool {
521        match self {
522            Self::Ethereum(inner) => inner.is_dynamic_fee(),
523            Self::Unknown(inner) => inner.is_dynamic_fee(),
524        }
525    }
526
527    fn kind(&self) -> alloy_primitives::TxKind {
528        match self {
529            Self::Ethereum(inner) => inner.kind(),
530            Self::Unknown(inner) => inner.kind(),
531        }
532    }
533
534    #[inline]
535    fn is_create(&self) -> bool {
536        match self {
537            Self::Ethereum(inner) => inner.is_create(),
538            Self::Unknown(inner) => inner.is_create(),
539        }
540    }
541
542    #[inline]
543    fn value(&self) -> U256 {
544        match self {
545            Self::Ethereum(inner) => inner.value(),
546            Self::Unknown(inner) => inner.value(),
547        }
548    }
549
550    #[inline]
551    fn input(&self) -> &Bytes {
552        match self {
553            Self::Ethereum(inner) => inner.input(),
554            Self::Unknown(inner) => inner.input(),
555        }
556    }
557
558    #[inline]
559    fn access_list(&self) -> Option<&AccessList> {
560        match self {
561            Self::Ethereum(inner) => inner.access_list(),
562            Self::Unknown(inner) => inner.access_list(),
563        }
564    }
565
566    #[inline]
567    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
568        match self {
569            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
570            Self::Unknown(inner) => inner.blob_versioned_hashes(),
571        }
572    }
573
574    #[inline]
575    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
576        match self {
577            Self::Ethereum(inner) => inner.authorization_list(),
578            Self::Unknown(inner) => inner.authorization_list(),
579        }
580    }
581}