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>
268 where
269 T: TryFrom<UnknownTxEnvelope>,
270 {
271 self.try_map_unknown(|inner| inner.try_into())
272 }
273
274 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 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 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 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 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 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}