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    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
268    where
269        T: TryFrom<UnknownTxEnvelope>,
270    {
271        self.try_map_unknown(|inner| inner.try_into())
272    }
273
274    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an
275    /// [`AnyTxEnvelope::Unknown`].
276    ///
277    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
278    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
279    pub fn try_map_unknown<T, E>(
280        self,
281        f: impl FnOnce(UnknownTxEnvelope) -> Result<T, E>,
282    ) -> Result<Either<TxEnvelope, T>, E> {
283        match self {
284            Self::Ethereum(tx) => Ok(Either::Left(tx)),
285            Self::Unknown(tx) => Ok(Either::Right(f(tx)?)),
286        }
287    }
288
289    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
290    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
291        match self.as_envelope() {
292            Some(TxEnvelope::Legacy(tx)) => Some(tx),
293            _ => None,
294        }
295    }
296
297    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
298    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
299        match self.as_envelope() {
300            Some(TxEnvelope::Eip2930(tx)) => Some(tx),
301            _ => None,
302        }
303    }
304
305    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
306    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
307        match self.as_envelope() {
308            Some(TxEnvelope::Eip1559(tx)) => Some(tx),
309            _ => None,
310        }
311    }
312
313    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
314    pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
315        match self.as_envelope() {
316            Some(TxEnvelope::Eip4844(tx)) => Some(tx),
317            _ => None,
318        }
319    }
320
321    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
322    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
323        match self.as_envelope() {
324            Some(TxEnvelope::Eip7702(tx)) => Some(tx),
325            _ => None,
326        }
327    }
328
329    /// Returns true if the transaction is a legacy transaction.
330    #[inline]
331    pub const fn is_legacy(&self) -> bool {
332        matches!(self.as_envelope(), Some(TxEnvelope::Legacy(_)))
333    }
334
335    /// Returns true if the transaction is an EIP-2930 transaction.
336    #[inline]
337    pub const fn is_eip2930(&self) -> bool {
338        matches!(self.as_envelope(), Some(TxEnvelope::Eip2930(_)))
339    }
340
341    /// Returns true if the transaction is an EIP-1559 transaction.
342    #[inline]
343    pub const fn is_eip1559(&self) -> bool {
344        matches!(self.as_envelope(), Some(TxEnvelope::Eip1559(_)))
345    }
346
347    /// Returns true if the transaction is an EIP-4844 transaction.
348    #[inline]
349    pub const fn is_eip4844(&self) -> bool {
350        matches!(self.as_envelope(), Some(TxEnvelope::Eip4844(_)))
351    }
352
353    /// Returns true if the transaction is an EIP-7702 transaction.
354    #[inline]
355    pub const fn is_eip7702(&self) -> bool {
356        matches!(self.as_envelope(), Some(TxEnvelope::Eip7702(_)))
357    }
358}
359
360impl Typed2718 for AnyTxEnvelope {
361    fn ty(&self) -> u8 {
362        match self {
363            Self::Ethereum(inner) => inner.ty(),
364            Self::Unknown(inner) => inner.ty(),
365        }
366    }
367}
368
369impl Encodable2718 for AnyTxEnvelope {
370    fn encode_2718_len(&self) -> usize {
371        match self {
372            Self::Ethereum(t) => t.encode_2718_len(),
373            Self::Unknown(_) => 1,
374        }
375    }
376
377    #[track_caller]
378    fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
379        match self {
380            Self::Ethereum(t) => t.encode_2718(out),
381            Self::Unknown(inner) => {
382                panic!(
383                    "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.",
384                    inner.as_ref().ty
385                )
386            }
387        }
388    }
389
390    fn trie_hash(&self) -> B256 {
391        match self {
392            Self::Ethereum(tx) => tx.trie_hash(),
393            Self::Unknown(inner) => inner.hash,
394        }
395    }
396}
397
398impl Decodable2718 for AnyTxEnvelope {
399    fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
400        TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
401    }
402
403    fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
404        TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
405    }
406}
407
408impl TransactionTrait for AnyTxEnvelope {
409    #[inline]
410    fn chain_id(&self) -> Option<ChainId> {
411        match self {
412            Self::Ethereum(inner) => inner.chain_id(),
413            Self::Unknown(inner) => inner.chain_id(),
414        }
415    }
416
417    #[inline]
418    fn nonce(&self) -> u64 {
419        match self {
420            Self::Ethereum(inner) => inner.nonce(),
421            Self::Unknown(inner) => inner.nonce(),
422        }
423    }
424
425    #[inline]
426    fn gas_limit(&self) -> u64 {
427        match self {
428            Self::Ethereum(inner) => inner.gas_limit(),
429            Self::Unknown(inner) => inner.gas_limit(),
430        }
431    }
432
433    #[inline]
434    fn gas_price(&self) -> Option<u128> {
435        match self {
436            Self::Ethereum(inner) => inner.gas_price(),
437            Self::Unknown(inner) => inner.gas_price(),
438        }
439    }
440
441    #[inline]
442    fn max_fee_per_gas(&self) -> u128 {
443        match self {
444            Self::Ethereum(inner) => inner.max_fee_per_gas(),
445            Self::Unknown(inner) => inner.max_fee_per_gas(),
446        }
447    }
448
449    #[inline]
450    fn max_priority_fee_per_gas(&self) -> Option<u128> {
451        match self {
452            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
453            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
454        }
455    }
456
457    #[inline]
458    fn max_fee_per_blob_gas(&self) -> Option<u128> {
459        match self {
460            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
461            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
462        }
463    }
464
465    #[inline]
466    fn priority_fee_or_price(&self) -> u128 {
467        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
468    }
469
470    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
471        match self {
472            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
473            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
474        }
475    }
476
477    #[inline]
478    fn is_dynamic_fee(&self) -> bool {
479        match self {
480            Self::Ethereum(inner) => inner.is_dynamic_fee(),
481            Self::Unknown(inner) => inner.is_dynamic_fee(),
482        }
483    }
484
485    fn kind(&self) -> alloy_primitives::TxKind {
486        match self {
487            Self::Ethereum(inner) => inner.kind(),
488            Self::Unknown(inner) => inner.kind(),
489        }
490    }
491
492    #[inline]
493    fn is_create(&self) -> bool {
494        match self {
495            Self::Ethereum(inner) => inner.is_create(),
496            Self::Unknown(inner) => inner.is_create(),
497        }
498    }
499
500    #[inline]
501    fn value(&self) -> U256 {
502        match self {
503            Self::Ethereum(inner) => inner.value(),
504            Self::Unknown(inner) => inner.value(),
505        }
506    }
507
508    #[inline]
509    fn input(&self) -> &Bytes {
510        match self {
511            Self::Ethereum(inner) => inner.input(),
512            Self::Unknown(inner) => inner.input(),
513        }
514    }
515
516    #[inline]
517    fn access_list(&self) -> Option<&AccessList> {
518        match self {
519            Self::Ethereum(inner) => inner.access_list(),
520            Self::Unknown(inner) => inner.access_list(),
521        }
522    }
523
524    #[inline]
525    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
526        match self {
527            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
528            Self::Unknown(inner) => inner.blob_versioned_hashes(),
529        }
530    }
531
532    #[inline]
533    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
534        match self {
535            Self::Ethereum(inner) => inner.authorization_list(),
536            Self::Unknown(inner) => inner.authorization_list(),
537        }
538    }
539}