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 as_envelope(&self) -> Option<&TxEnvelope> {
220 match self {
221 Self::Ethereum(inner) => Some(inner),
222 Self::Unknown(_) => None,
223 }
224 }
225
226 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 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 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 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 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 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 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 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 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 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}