1use crate::{UnknownTxEnvelope, UnknownTypedTransaction};
2use alloy_consensus::{
3 error::ValueError, transaction::Either, SignableTransaction, Signed,
4 Transaction as TransactionTrait, TxEip1559, TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope,
5 TxLegacy, Typed2718, TypedTransaction,
6};
7use alloy_eips::{
8 eip2718::{Decodable2718, Encodable2718},
9 eip7702::SignedAuthorization,
10};
11use alloy_primitives::{bytes::BufMut, Bytes, ChainId, Signature, B256, U256};
12use alloy_rpc_types_eth::{AccessList, TransactionRequest};
13use alloy_serde::WithOtherFields;
14
15#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[serde(untagged)]
18#[doc(alias = "AnyTypedTx")]
19pub enum AnyTypedTransaction {
20 Ethereum(TypedTransaction),
22 Unknown(UnknownTypedTransaction),
24}
25
26impl From<UnknownTypedTransaction> for AnyTypedTransaction {
27 fn from(value: UnknownTypedTransaction) -> Self {
28 Self::Unknown(value)
29 }
30}
31
32impl From<TypedTransaction> for AnyTypedTransaction {
33 fn from(value: TypedTransaction) -> Self {
34 Self::Ethereum(value)
35 }
36}
37
38impl From<AnyTxEnvelope> for AnyTypedTransaction {
39 fn from(value: AnyTxEnvelope) -> Self {
40 match value {
41 AnyTxEnvelope::Ethereum(tx) => Self::Ethereum(tx.into()),
42 AnyTxEnvelope::Unknown(UnknownTxEnvelope { inner, .. }) => inner.into(),
43 }
44 }
45}
46
47impl From<AnyTypedTransaction> for WithOtherFields<TransactionRequest> {
48 fn from(value: AnyTypedTransaction) -> Self {
49 match value {
50 AnyTypedTransaction::Ethereum(tx) => Self::new(tx.into()),
51 AnyTypedTransaction::Unknown(UnknownTypedTransaction { ty, mut fields, .. }) => {
52 fields.insert("type".to_string(), serde_json::Value::Number(ty.0.into()));
53 Self { inner: Default::default(), other: fields }
54 }
55 }
56 }
57}
58
59impl From<AnyTxEnvelope> for WithOtherFields<TransactionRequest> {
60 fn from(value: AnyTxEnvelope) -> Self {
61 AnyTypedTransaction::from(value).into()
62 }
63}
64
65impl TransactionTrait for AnyTypedTransaction {
66 #[inline]
67 fn chain_id(&self) -> Option<ChainId> {
68 match self {
69 Self::Ethereum(inner) => inner.chain_id(),
70 Self::Unknown(inner) => inner.chain_id(),
71 }
72 }
73
74 #[inline]
75 fn nonce(&self) -> u64 {
76 match self {
77 Self::Ethereum(inner) => inner.nonce(),
78 Self::Unknown(inner) => inner.nonce(),
79 }
80 }
81
82 #[inline]
83 fn gas_limit(&self) -> u64 {
84 match self {
85 Self::Ethereum(inner) => inner.gas_limit(),
86 Self::Unknown(inner) => inner.gas_limit(),
87 }
88 }
89
90 #[inline]
91 fn gas_price(&self) -> Option<u128> {
92 match self {
93 Self::Ethereum(inner) => inner.gas_price(),
94 Self::Unknown(inner) => inner.gas_price(),
95 }
96 }
97
98 #[inline]
99 fn max_fee_per_gas(&self) -> u128 {
100 match self {
101 Self::Ethereum(inner) => inner.max_fee_per_gas(),
102 Self::Unknown(inner) => inner.max_fee_per_gas(),
103 }
104 }
105
106 #[inline]
107 fn max_priority_fee_per_gas(&self) -> Option<u128> {
108 match self {
109 Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
110 Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
111 }
112 }
113
114 #[inline]
115 fn max_fee_per_blob_gas(&self) -> Option<u128> {
116 match self {
117 Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
118 Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
119 }
120 }
121
122 #[inline]
123 fn priority_fee_or_price(&self) -> u128 {
124 self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
125 }
126
127 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
128 match self {
129 Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
130 Self::Unknown(inner) => inner.effective_gas_price(base_fee),
131 }
132 }
133
134 #[inline]
135 fn is_dynamic_fee(&self) -> bool {
136 match self {
137 Self::Ethereum(inner) => inner.is_dynamic_fee(),
138 Self::Unknown(inner) => inner.is_dynamic_fee(),
139 }
140 }
141
142 fn kind(&self) -> alloy_primitives::TxKind {
143 match self {
144 Self::Ethereum(inner) => inner.kind(),
145 Self::Unknown(inner) => inner.kind(),
146 }
147 }
148
149 #[inline]
150 fn is_create(&self) -> bool {
151 match self {
152 Self::Ethereum(inner) => inner.is_create(),
153 Self::Unknown(inner) => inner.is_create(),
154 }
155 }
156
157 #[inline]
158 fn value(&self) -> U256 {
159 match self {
160 Self::Ethereum(inner) => inner.value(),
161 Self::Unknown(inner) => inner.value(),
162 }
163 }
164
165 #[inline]
166 fn input(&self) -> &Bytes {
167 match self {
168 Self::Ethereum(inner) => inner.input(),
169 Self::Unknown(inner) => inner.input(),
170 }
171 }
172
173 #[inline]
174 fn access_list(&self) -> Option<&AccessList> {
175 match self {
176 Self::Ethereum(inner) => inner.access_list(),
177 Self::Unknown(inner) => inner.access_list(),
178 }
179 }
180
181 #[inline]
182 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
183 match self {
184 Self::Ethereum(inner) => inner.blob_versioned_hashes(),
185 Self::Unknown(inner) => inner.blob_versioned_hashes(),
186 }
187 }
188
189 #[inline]
190 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
191 match self {
192 Self::Ethereum(inner) => inner.authorization_list(),
193 Self::Unknown(inner) => inner.authorization_list(),
194 }
195 }
196}
197
198impl Typed2718 for AnyTypedTransaction {
199 fn ty(&self) -> u8 {
200 match self {
201 Self::Ethereum(inner) => inner.ty(),
202 Self::Unknown(inner) => inner.ty(),
203 }
204 }
205}
206
207impl SignableTransaction<Signature> for AnyTypedTransaction {
208 fn set_chain_id(&mut self, chain_id: ChainId) {
209 match self {
210 Self::Ethereum(typed_tx) => typed_tx.set_chain_id(chain_id),
211 Self::Unknown(_) => (),
212 }
213 }
214
215 fn encode_for_signing(&self, out: &mut dyn BufMut) {
216 match self {
217 Self::Ethereum(typed_tx) => typed_tx.encode_for_signing(out),
218 Self::Unknown(_) => (),
219 }
220 }
221
222 fn payload_len_for_signature(&self) -> usize {
223 match self {
224 Self::Ethereum(typed_tx) => typed_tx.payload_len_for_signature(),
225 Self::Unknown(_) => 0,
226 }
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
232#[serde(untagged)]
233#[doc(alias = "AnyTransactionEnvelope")]
234pub enum AnyTxEnvelope {
235 Ethereum(TxEnvelope),
237 Unknown(UnknownTxEnvelope),
239}
240
241impl From<Signed<AnyTypedTransaction>> for AnyTxEnvelope {
242 fn from(value: Signed<AnyTypedTransaction>) -> Self {
243 let sig = *value.signature();
244 let tx = value.strip_signature();
245 match tx {
246 AnyTypedTransaction::Ethereum(typed_tx) => Self::Ethereum(match typed_tx {
247 TypedTransaction::Legacy(tx) => TxEnvelope::Legacy(tx.into_signed(sig)),
248 TypedTransaction::Eip2930(tx) => TxEnvelope::Eip2930(tx.into_signed(sig)),
249 TypedTransaction::Eip1559(tx) => TxEnvelope::Eip1559(tx.into_signed(sig)),
250 TypedTransaction::Eip4844(tx) => TxEnvelope::Eip4844(tx.into_signed(sig)),
251 TypedTransaction::Eip7702(tx) => TxEnvelope::Eip7702(tx.into_signed(sig)),
252 }),
253 AnyTypedTransaction::Unknown(unknown_tx) => {
254 Self::Unknown(UnknownTxEnvelope { hash: B256::ZERO, inner: unknown_tx })
255 }
256 }
257 }
258}
259
260impl AnyTxEnvelope {
261 pub const fn is_ethereum(&self) -> bool {
263 matches!(self, Self::Ethereum(_))
264 }
265
266 pub const fn is_unknown(&self) -> bool {
268 matches!(self, Self::Unknown(_))
269 }
270
271 pub const fn as_envelope(&self) -> Option<&TxEnvelope> {
273 match self {
274 Self::Ethereum(inner) => Some(inner),
275 Self::Unknown(_) => None,
276 }
277 }
278
279 pub const fn as_unknown(&self) -> Option<&UnknownTxEnvelope> {
281 match self {
282 Self::Unknown(inner) => Some(inner),
283 Self::Ethereum(_) => None,
284 }
285 }
286
287 pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<Self>> {
290 match self {
291 Self::Ethereum(inner) => Ok(inner),
292 this => Err(ValueError::new_static(this, "unknown transaction envelope")),
293 }
294 }
295
296 pub fn try_into_unknown(self) -> Result<UnknownTxEnvelope, Self> {
299 match self {
300 Self::Unknown(inner) => Ok(inner),
301 this => Err(this),
302 }
303 }
304
305 pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
334 where
335 T: TryFrom<UnknownTxEnvelope>,
336 {
337 self.try_map_unknown(|inner| inner.try_into())
338 }
339
340 pub fn try_map_unknown<T, E>(
365 self,
366 f: impl FnOnce(UnknownTxEnvelope) -> Result<T, E>,
367 ) -> Result<Either<TxEnvelope, T>, E> {
368 match self {
369 Self::Ethereum(tx) => Ok(Either::Left(tx)),
370 Self::Unknown(tx) => Ok(Either::Right(f(tx)?)),
371 }
372 }
373
374 pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
376 match self.as_envelope() {
377 Some(TxEnvelope::Legacy(tx)) => Some(tx),
378 _ => None,
379 }
380 }
381
382 pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
384 match self.as_envelope() {
385 Some(TxEnvelope::Eip2930(tx)) => Some(tx),
386 _ => None,
387 }
388 }
389
390 pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
392 match self.as_envelope() {
393 Some(TxEnvelope::Eip1559(tx)) => Some(tx),
394 _ => None,
395 }
396 }
397
398 pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
400 match self.as_envelope() {
401 Some(TxEnvelope::Eip4844(tx)) => Some(tx),
402 _ => None,
403 }
404 }
405
406 pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
408 match self.as_envelope() {
409 Some(TxEnvelope::Eip7702(tx)) => Some(tx),
410 _ => None,
411 }
412 }
413
414 #[inline]
416 pub const fn is_legacy(&self) -> bool {
417 matches!(self.as_envelope(), Some(TxEnvelope::Legacy(_)))
418 }
419
420 #[inline]
422 pub const fn is_eip2930(&self) -> bool {
423 matches!(self.as_envelope(), Some(TxEnvelope::Eip2930(_)))
424 }
425
426 #[inline]
428 pub const fn is_eip1559(&self) -> bool {
429 matches!(self.as_envelope(), Some(TxEnvelope::Eip1559(_)))
430 }
431
432 #[inline]
434 pub const fn is_eip4844(&self) -> bool {
435 matches!(self.as_envelope(), Some(TxEnvelope::Eip4844(_)))
436 }
437
438 #[inline]
440 pub const fn is_eip7702(&self) -> bool {
441 matches!(self.as_envelope(), Some(TxEnvelope::Eip7702(_)))
442 }
443}
444
445impl Typed2718 for AnyTxEnvelope {
446 fn ty(&self) -> u8 {
447 match self {
448 Self::Ethereum(inner) => inner.ty(),
449 Self::Unknown(inner) => inner.ty(),
450 }
451 }
452}
453
454impl Encodable2718 for AnyTxEnvelope {
455 fn encode_2718_len(&self) -> usize {
456 match self {
457 Self::Ethereum(t) => t.encode_2718_len(),
458 Self::Unknown(_) => 1,
459 }
460 }
461
462 #[track_caller]
463 fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
464 match self {
465 Self::Ethereum(t) => t.encode_2718(out),
466 Self::Unknown(inner) => {
467 panic!(
468 "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.",
469 inner.as_ref().ty
470 )
471 }
472 }
473 }
474
475 fn trie_hash(&self) -> B256 {
476 match self {
477 Self::Ethereum(tx) => tx.trie_hash(),
478 Self::Unknown(inner) => inner.hash,
479 }
480 }
481}
482
483impl Decodable2718 for AnyTxEnvelope {
484 fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
485 TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
486 }
487
488 fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
489 TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
490 }
491}
492
493impl TransactionTrait for AnyTxEnvelope {
494 #[inline]
495 fn chain_id(&self) -> Option<ChainId> {
496 match self {
497 Self::Ethereum(inner) => inner.chain_id(),
498 Self::Unknown(inner) => inner.chain_id(),
499 }
500 }
501
502 #[inline]
503 fn nonce(&self) -> u64 {
504 match self {
505 Self::Ethereum(inner) => inner.nonce(),
506 Self::Unknown(inner) => inner.nonce(),
507 }
508 }
509
510 #[inline]
511 fn gas_limit(&self) -> u64 {
512 match self {
513 Self::Ethereum(inner) => inner.gas_limit(),
514 Self::Unknown(inner) => inner.gas_limit(),
515 }
516 }
517
518 #[inline]
519 fn gas_price(&self) -> Option<u128> {
520 match self {
521 Self::Ethereum(inner) => inner.gas_price(),
522 Self::Unknown(inner) => inner.gas_price(),
523 }
524 }
525
526 #[inline]
527 fn max_fee_per_gas(&self) -> u128 {
528 match self {
529 Self::Ethereum(inner) => inner.max_fee_per_gas(),
530 Self::Unknown(inner) => inner.max_fee_per_gas(),
531 }
532 }
533
534 #[inline]
535 fn max_priority_fee_per_gas(&self) -> Option<u128> {
536 match self {
537 Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
538 Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
539 }
540 }
541
542 #[inline]
543 fn max_fee_per_blob_gas(&self) -> Option<u128> {
544 match self {
545 Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
546 Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
547 }
548 }
549
550 #[inline]
551 fn priority_fee_or_price(&self) -> u128 {
552 self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
553 }
554
555 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
556 match self {
557 Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
558 Self::Unknown(inner) => inner.effective_gas_price(base_fee),
559 }
560 }
561
562 #[inline]
563 fn is_dynamic_fee(&self) -> bool {
564 match self {
565 Self::Ethereum(inner) => inner.is_dynamic_fee(),
566 Self::Unknown(inner) => inner.is_dynamic_fee(),
567 }
568 }
569
570 fn kind(&self) -> alloy_primitives::TxKind {
571 match self {
572 Self::Ethereum(inner) => inner.kind(),
573 Self::Unknown(inner) => inner.kind(),
574 }
575 }
576
577 #[inline]
578 fn is_create(&self) -> bool {
579 match self {
580 Self::Ethereum(inner) => inner.is_create(),
581 Self::Unknown(inner) => inner.is_create(),
582 }
583 }
584
585 #[inline]
586 fn value(&self) -> U256 {
587 match self {
588 Self::Ethereum(inner) => inner.value(),
589 Self::Unknown(inner) => inner.value(),
590 }
591 }
592
593 #[inline]
594 fn input(&self) -> &Bytes {
595 match self {
596 Self::Ethereum(inner) => inner.input(),
597 Self::Unknown(inner) => inner.input(),
598 }
599 }
600
601 #[inline]
602 fn access_list(&self) -> Option<&AccessList> {
603 match self {
604 Self::Ethereum(inner) => inner.access_list(),
605 Self::Unknown(inner) => inner.access_list(),
606 }
607 }
608
609 #[inline]
610 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
611 match self {
612 Self::Ethereum(inner) => inner.blob_versioned_hashes(),
613 Self::Unknown(inner) => inner.blob_versioned_hashes(),
614 }
615 }
616
617 #[inline]
618 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
619 match self {
620 Self::Ethereum(inner) => inner.authorization_list(),
621 Self::Unknown(inner) => inner.authorization_list(),
622 }
623 }
624}