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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[serde(untagged)]
17#[doc(alias = "AnyTypedTx")]
18pub enum AnyTypedTransaction {
19 Ethereum(TypedTransaction),
21 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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
208#[serde(untagged)]
209#[doc(alias = "AnyTransactionEnvelope")]
210pub enum AnyTxEnvelope {
211 Ethereum(TxEnvelope),
213 Unknown(UnknownTxEnvelope),
215}
216
217impl AnyTxEnvelope {
218 pub const fn is_ethereum(&self) -> bool {
220 matches!(self, Self::Ethereum(_))
221 }
222
223 pub const fn is_unknown(&self) -> bool {
225 matches!(self, Self::Unknown(_))
226 }
227
228 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 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 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 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 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 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 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 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 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 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 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 #[inline]
373 pub const fn is_legacy(&self) -> bool {
374 matches!(self.as_envelope(), Some(TxEnvelope::Legacy(_)))
375 }
376
377 #[inline]
379 pub const fn is_eip2930(&self) -> bool {
380 matches!(self.as_envelope(), Some(TxEnvelope::Eip2930(_)))
381 }
382
383 #[inline]
385 pub const fn is_eip1559(&self) -> bool {
386 matches!(self.as_envelope(), Some(TxEnvelope::Eip1559(_)))
387 }
388
389 #[inline]
391 pub const fn is_eip4844(&self) -> bool {
392 matches!(self.as_envelope(), Some(TxEnvelope::Eip4844(_)))
393 }
394
395 #[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}