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
330impl Typed2718 for AnyTxEnvelope {
331    fn ty(&self) -> u8 {
332        match self {
333            Self::Ethereum(inner) => inner.ty(),
334            Self::Unknown(inner) => inner.ty(),
335        }
336    }
337}
338
339impl Encodable2718 for AnyTxEnvelope {
340    fn encode_2718_len(&self) -> usize {
341        match self {
342            Self::Ethereum(t) => t.encode_2718_len(),
343            Self::Unknown(_) => 1,
344        }
345    }
346
347    #[track_caller]
348    fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
349        match self {
350            Self::Ethereum(t) => t.encode_2718(out),
351            Self::Unknown(inner) => {
352                panic!(
353                    "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.",
354                    inner.as_ref().ty
355                )
356            }
357        }
358    }
359
360    fn trie_hash(&self) -> B256 {
361        match self {
362            Self::Ethereum(tx) => tx.trie_hash(),
363            Self::Unknown(inner) => inner.hash,
364        }
365    }
366}
367
368impl Decodable2718 for AnyTxEnvelope {
369    fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
370        TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
371    }
372
373    fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
374        TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
375    }
376}
377
378impl TransactionTrait for AnyTxEnvelope {
379    #[inline]
380    fn chain_id(&self) -> Option<ChainId> {
381        match self {
382            Self::Ethereum(inner) => inner.chain_id(),
383            Self::Unknown(inner) => inner.chain_id(),
384        }
385    }
386
387    #[inline]
388    fn nonce(&self) -> u64 {
389        match self {
390            Self::Ethereum(inner) => inner.nonce(),
391            Self::Unknown(inner) => inner.nonce(),
392        }
393    }
394
395    #[inline]
396    fn gas_limit(&self) -> u64 {
397        match self {
398            Self::Ethereum(inner) => inner.gas_limit(),
399            Self::Unknown(inner) => inner.gas_limit(),
400        }
401    }
402
403    #[inline]
404    fn gas_price(&self) -> Option<u128> {
405        match self {
406            Self::Ethereum(inner) => inner.gas_price(),
407            Self::Unknown(inner) => inner.gas_price(),
408        }
409    }
410
411    #[inline]
412    fn max_fee_per_gas(&self) -> u128 {
413        match self {
414            Self::Ethereum(inner) => inner.max_fee_per_gas(),
415            Self::Unknown(inner) => inner.max_fee_per_gas(),
416        }
417    }
418
419    #[inline]
420    fn max_priority_fee_per_gas(&self) -> Option<u128> {
421        match self {
422            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
423            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
424        }
425    }
426
427    #[inline]
428    fn max_fee_per_blob_gas(&self) -> Option<u128> {
429        match self {
430            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
431            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
432        }
433    }
434
435    #[inline]
436    fn priority_fee_or_price(&self) -> u128 {
437        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
438    }
439
440    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
441        match self {
442            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
443            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
444        }
445    }
446
447    #[inline]
448    fn is_dynamic_fee(&self) -> bool {
449        match self {
450            Self::Ethereum(inner) => inner.is_dynamic_fee(),
451            Self::Unknown(inner) => inner.is_dynamic_fee(),
452        }
453    }
454
455    fn kind(&self) -> alloy_primitives::TxKind {
456        match self {
457            Self::Ethereum(inner) => inner.kind(),
458            Self::Unknown(inner) => inner.kind(),
459        }
460    }
461
462    #[inline]
463    fn is_create(&self) -> bool {
464        match self {
465            Self::Ethereum(inner) => inner.is_create(),
466            Self::Unknown(inner) => inner.is_create(),
467        }
468    }
469
470    #[inline]
471    fn value(&self) -> U256 {
472        match self {
473            Self::Ethereum(inner) => inner.value(),
474            Self::Unknown(inner) => inner.value(),
475        }
476    }
477
478    #[inline]
479    fn input(&self) -> &Bytes {
480        match self {
481            Self::Ethereum(inner) => inner.input(),
482            Self::Unknown(inner) => inner.input(),
483        }
484    }
485
486    #[inline]
487    fn access_list(&self) -> Option<&AccessList> {
488        match self {
489            Self::Ethereum(inner) => inner.access_list(),
490            Self::Unknown(inner) => inner.access_list(),
491        }
492    }
493
494    #[inline]
495    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
496        match self {
497            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
498            Self::Unknown(inner) => inner.blob_versioned_hashes(),
499        }
500    }
501
502    #[inline]
503    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
504        match self {
505            Self::Ethereum(inner) => inner.authorization_list(),
506            Self::Unknown(inner) => inner.authorization_list(),
507        }
508    }
509}