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 the inner Ethereum transaction envelope, if it is an Ethereum transaction.
219    pub const fn as_envelope(&self) -> Option<&TxEnvelope> {
220        match self {
221            Self::Ethereum(inner) => Some(inner),
222            Self::Unknown(_) => None,
223        }
224    }
225
226    /// Returns the inner [`UnknownTxEnvelope`] if it is an unknown variant.
227    pub const fn as_unknown(&self) -> Option<&UnknownTxEnvelope> {
228        match self {
229            Self::Unknown(inner) => Some(inner),
230            Self::Ethereum(_) => None,
231        }
232    }
233
234    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
235    /// If the transaction is not an Ethereum transaction, it is returned as an error.
236    pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<Self>> {
237        match self {
238            Self::Ethereum(inner) => Ok(inner),
239            this => Err(ValueError::new_static(this, "unknown transaction envelope")),
240        }
241    }
242
243    /// Returns the inner [`UnknownTxEnvelope`], if it is an unknown transaction.
244    /// If the transaction is not an unknown transaction, it is returned as an error.
245    pub fn try_into_unknown(self) -> Result<UnknownTxEnvelope, Self> {
246        match self {
247            Self::Unknown(inner) => Ok(inner),
248            this => Err(this),
249        }
250    }
251
252    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an unknown
253    /// variant.
254    ///
255    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
256    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
257    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
258    where
259        T: TryFrom<UnknownTxEnvelope>,
260    {
261        self.try_map_unknown(|inner| inner.try_into())
262    }
263
264    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an
265    /// [`AnyTxEnvelope::Unknown`].
266    ///
267    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
268    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
269    pub fn try_map_unknown<T, E>(
270        self,
271        f: impl FnOnce(UnknownTxEnvelope) -> Result<T, E>,
272    ) -> Result<Either<TxEnvelope, T>, E> {
273        match self {
274            Self::Ethereum(tx) => Ok(Either::Left(tx)),
275            Self::Unknown(tx) => Ok(Either::Right(f(tx)?)),
276        }
277    }
278
279    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
280    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
281        match self.as_envelope() {
282            Some(TxEnvelope::Legacy(tx)) => Some(tx),
283            _ => None,
284        }
285    }
286
287    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
288    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
289        match self.as_envelope() {
290            Some(TxEnvelope::Eip2930(tx)) => Some(tx),
291            _ => None,
292        }
293    }
294
295    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
296    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
297        match self.as_envelope() {
298            Some(TxEnvelope::Eip1559(tx)) => Some(tx),
299            _ => None,
300        }
301    }
302
303    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
304    pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
305        match self.as_envelope() {
306            Some(TxEnvelope::Eip4844(tx)) => Some(tx),
307            _ => None,
308        }
309    }
310
311    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
312    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
313        match self.as_envelope() {
314            Some(TxEnvelope::Eip7702(tx)) => Some(tx),
315            _ => None,
316        }
317    }
318}
319
320impl Typed2718 for AnyTxEnvelope {
321    fn ty(&self) -> u8 {
322        match self {
323            Self::Ethereum(inner) => inner.ty(),
324            Self::Unknown(inner) => inner.ty(),
325        }
326    }
327}
328
329impl Encodable2718 for AnyTxEnvelope {
330    fn encode_2718_len(&self) -> usize {
331        match self {
332            Self::Ethereum(t) => t.encode_2718_len(),
333            Self::Unknown(_) => 1,
334        }
335    }
336
337    #[track_caller]
338    fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
339        match self {
340            Self::Ethereum(t) => t.encode_2718(out),
341            Self::Unknown(inner) => {
342                panic!(
343                    "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.",
344                    inner.as_ref().ty
345                )
346            }
347        }
348    }
349
350    fn trie_hash(&self) -> B256 {
351        match self {
352            Self::Ethereum(tx) => tx.trie_hash(),
353            Self::Unknown(inner) => inner.hash,
354        }
355    }
356}
357
358impl Decodable2718 for AnyTxEnvelope {
359    fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
360        TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
361    }
362
363    fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
364        TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
365    }
366}
367
368impl TransactionTrait for AnyTxEnvelope {
369    #[inline]
370    fn chain_id(&self) -> Option<ChainId> {
371        match self {
372            Self::Ethereum(inner) => inner.chain_id(),
373            Self::Unknown(inner) => inner.chain_id(),
374        }
375    }
376
377    #[inline]
378    fn nonce(&self) -> u64 {
379        match self {
380            Self::Ethereum(inner) => inner.nonce(),
381            Self::Unknown(inner) => inner.nonce(),
382        }
383    }
384
385    #[inline]
386    fn gas_limit(&self) -> u64 {
387        match self {
388            Self::Ethereum(inner) => inner.gas_limit(),
389            Self::Unknown(inner) => inner.gas_limit(),
390        }
391    }
392
393    #[inline]
394    fn gas_price(&self) -> Option<u128> {
395        match self {
396            Self::Ethereum(inner) => inner.gas_price(),
397            Self::Unknown(inner) => inner.gas_price(),
398        }
399    }
400
401    #[inline]
402    fn max_fee_per_gas(&self) -> u128 {
403        match self {
404            Self::Ethereum(inner) => inner.max_fee_per_gas(),
405            Self::Unknown(inner) => inner.max_fee_per_gas(),
406        }
407    }
408
409    #[inline]
410    fn max_priority_fee_per_gas(&self) -> Option<u128> {
411        match self {
412            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
413            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
414        }
415    }
416
417    #[inline]
418    fn max_fee_per_blob_gas(&self) -> Option<u128> {
419        match self {
420            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
421            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
422        }
423    }
424
425    #[inline]
426    fn priority_fee_or_price(&self) -> u128 {
427        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
428    }
429
430    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
431        match self {
432            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
433            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
434        }
435    }
436
437    #[inline]
438    fn is_dynamic_fee(&self) -> bool {
439        match self {
440            Self::Ethereum(inner) => inner.is_dynamic_fee(),
441            Self::Unknown(inner) => inner.is_dynamic_fee(),
442        }
443    }
444
445    fn kind(&self) -> alloy_primitives::TxKind {
446        match self {
447            Self::Ethereum(inner) => inner.kind(),
448            Self::Unknown(inner) => inner.kind(),
449        }
450    }
451
452    #[inline]
453    fn is_create(&self) -> bool {
454        match self {
455            Self::Ethereum(inner) => inner.is_create(),
456            Self::Unknown(inner) => inner.is_create(),
457        }
458    }
459
460    #[inline]
461    fn value(&self) -> U256 {
462        match self {
463            Self::Ethereum(inner) => inner.value(),
464            Self::Unknown(inner) => inner.value(),
465        }
466    }
467
468    #[inline]
469    fn input(&self) -> &Bytes {
470        match self {
471            Self::Ethereum(inner) => inner.input(),
472            Self::Unknown(inner) => inner.input(),
473        }
474    }
475
476    #[inline]
477    fn access_list(&self) -> Option<&AccessList> {
478        match self {
479            Self::Ethereum(inner) => inner.access_list(),
480            Self::Unknown(inner) => inner.access_list(),
481        }
482    }
483
484    #[inline]
485    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
486        match self {
487            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
488            Self::Unknown(inner) => inner.blob_versioned_hashes(),
489        }
490    }
491
492    #[inline]
493    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
494        match self {
495            Self::Ethereum(inner) => inner.authorization_list(),
496            Self::Unknown(inner) => inner.authorization_list(),
497        }
498    }
499}