1pub use crate::transaction::envelope::EthereumTypedTransaction;
2use crate::{
3 error::ValueError,
4 private::alloy_eips::eip2718::Eip2718Error,
5 transaction::{
6 eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
7 RlpEcdsaDecodableTx, RlpEcdsaEncodableTx,
8 },
9 EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy,
10 TxType,
11};
12use alloy_eips::{eip2718::Eip2718Result, Typed2718};
13use alloy_primitives::{ChainId, Signature, TxHash};
14use alloy_rlp::{Buf, BufMut, Decodable};
15
16pub type TypedTransaction = EthereumTypedTransaction<TxEip4844Variant>;
18
19impl<Eip4844> From<TxLegacy> for EthereumTypedTransaction<Eip4844> {
20 fn from(tx: TxLegacy) -> Self {
21 Self::Legacy(tx)
22 }
23}
24
25impl<Eip4844> From<TxEip2930> for EthereumTypedTransaction<Eip4844> {
26 fn from(tx: TxEip2930) -> Self {
27 Self::Eip2930(tx)
28 }
29}
30
31impl<Eip4844> From<TxEip1559> for EthereumTypedTransaction<Eip4844> {
32 fn from(tx: TxEip1559) -> Self {
33 Self::Eip1559(tx)
34 }
35}
36
37impl<Eip4844: From<TxEip4844>> From<TxEip4844> for EthereumTypedTransaction<Eip4844> {
38 fn from(tx: TxEip4844) -> Self {
39 Self::Eip4844(tx.into())
40 }
41}
42
43impl<T, Eip4844: From<TxEip4844WithSidecar<T>>> From<TxEip4844WithSidecar<T>>
44 for EthereumTypedTransaction<Eip4844>
45{
46 fn from(tx: TxEip4844WithSidecar<T>) -> Self {
47 Self::Eip4844(tx.into())
48 }
49}
50
51impl<Sidecar, Eip4844: From<TxEip4844Variant<Sidecar>>> From<TxEip4844Variant<Sidecar>>
52 for EthereumTypedTransaction<Eip4844>
53{
54 fn from(tx: TxEip4844Variant<Sidecar>) -> Self {
55 Self::Eip4844(tx.into())
56 }
57}
58
59impl<Eip4844> From<TxEip7702> for EthereumTypedTransaction<Eip4844> {
60 fn from(tx: TxEip7702) -> Self {
61 Self::Eip7702(tx)
62 }
63}
64
65impl<Eip4844> From<EthereumTxEnvelope<Eip4844>> for EthereumTypedTransaction<Eip4844> {
66 fn from(envelope: EthereumTxEnvelope<Eip4844>) -> Self {
67 match envelope {
68 EthereumTxEnvelope::Legacy(tx) => Self::Legacy(tx.strip_signature()),
69 EthereumTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.strip_signature()),
70 EthereumTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.strip_signature()),
71 EthereumTxEnvelope::Eip4844(tx) => Self::Eip4844(tx.strip_signature()),
72 EthereumTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.strip_signature()),
73 }
74 }
75}
76
77impl<T> From<EthereumTypedTransaction<TxEip4844WithSidecar<T>>>
78 for EthereumTypedTransaction<TxEip4844>
79{
80 fn from(value: EthereumTypedTransaction<TxEip4844WithSidecar<T>>) -> Self {
81 value.map_eip4844(|eip4844| eip4844.into())
82 }
83}
84
85impl<T> From<EthereumTypedTransaction<TxEip4844Variant<T>>>
86 for EthereumTypedTransaction<TxEip4844>
87{
88 fn from(value: EthereumTypedTransaction<TxEip4844Variant<T>>) -> Self {
89 value.map_eip4844(|eip4844| eip4844.into())
90 }
91}
92
93impl<T> From<EthereumTypedTransaction<TxEip4844>>
94 for EthereumTypedTransaction<TxEip4844Variant<T>>
95{
96 fn from(value: EthereumTypedTransaction<TxEip4844>) -> Self {
97 value.map_eip4844(|eip4844| eip4844.into())
98 }
99}
100
101impl<Eip4844> EthereumTypedTransaction<Eip4844> {
102 pub fn map_eip4844<U>(self, mut f: impl FnMut(Eip4844) -> U) -> EthereumTypedTransaction<U> {
107 match self {
108 Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx),
109 Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx),
110 Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx),
111 Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(f(tx)),
112 Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx),
113 }
114 }
115
116 pub fn into_envelope(self, signature: Signature) -> EthereumTxEnvelope<Eip4844> {
118 match self {
119 Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx.into_signed(signature)),
120 Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx.into_signed(signature)),
121 Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx.into_signed(signature)),
122 Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(Signed::new_unhashed(tx, signature)),
123 Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx.into_signed(signature)),
124 }
125 }
126}
127
128impl<Eip4844: RlpEcdsaDecodableTx> EthereumTypedTransaction<Eip4844> {
129 pub fn decode_unsigned(buf: &mut &[u8]) -> Eip2718Result<Self> {
131 if buf.is_empty() {
132 return Err(alloy_rlp::Error::InputTooShort.into());
133 }
134
135 let first_byte = buf[0];
136
137 if first_byte >= 0xc0 {
139 return Ok(Self::Legacy(TxLegacy::decode(buf)?));
140 }
141
142 let tx_type = buf.get_u8();
143 match tx_type {
144 0x00 => Ok(Self::Legacy(TxLegacy::decode(buf)?)),
145 0x01 => Ok(Self::Eip2930(TxEip2930::decode(buf)?)),
146 0x02 => Ok(Self::Eip1559(TxEip1559::decode(buf)?)),
147 0x03 => Ok(Self::Eip4844(Eip4844::rlp_decode(buf)?)),
148 0x04 => Ok(Self::Eip7702(TxEip7702::decode(buf)?)),
149 _ => Err(Eip2718Error::UnexpectedType(tx_type)),
150 }
151 }
152}
153
154impl<Eip4844: RlpEcdsaEncodableTx> EthereumTypedTransaction<Eip4844> {
155 #[doc(alias = "transaction_type")]
157 pub const fn tx_type(&self) -> TxType {
158 match self {
159 Self::Legacy(_) => TxType::Legacy,
160 Self::Eip2930(_) => TxType::Eip2930,
161 Self::Eip1559(_) => TxType::Eip1559,
162 Self::Eip4844(_) => TxType::Eip4844,
163 Self::Eip7702(_) => TxType::Eip7702,
164 }
165 }
166
167 pub const fn legacy(&self) -> Option<&TxLegacy> {
169 match self {
170 Self::Legacy(tx) => Some(tx),
171 _ => None,
172 }
173 }
174
175 pub const fn eip2930(&self) -> Option<&TxEip2930> {
177 match self {
178 Self::Eip2930(tx) => Some(tx),
179 _ => None,
180 }
181 }
182
183 pub const fn eip1559(&self) -> Option<&TxEip1559> {
185 match self {
186 Self::Eip1559(tx) => Some(tx),
187 _ => None,
188 }
189 }
190
191 pub const fn eip7702(&self) -> Option<&TxEip7702> {
193 match self {
194 Self::Eip7702(tx) => Some(tx),
195 _ => None,
196 }
197 }
198
199 pub fn try_into_legacy(self) -> Result<TxLegacy, ValueError<Self>> {
201 match self {
202 Self::Legacy(tx) => Ok(tx),
203 _ => Err(ValueError::new(self, "Expected legacy transaction")),
204 }
205 }
206
207 pub fn try_into_eip2930(self) -> Result<TxEip2930, ValueError<Self>> {
209 match self {
210 Self::Eip2930(tx) => Ok(tx),
211 _ => Err(ValueError::new(self, "Expected EIP-2930 transaction")),
212 }
213 }
214
215 pub fn try_into_eip4844(self) -> Result<Eip4844, ValueError<Self>> {
217 match self {
218 Self::Eip4844(tx) => Ok(tx),
219 _ => Err(ValueError::new(self, "Expected EIP-4844 transaction")),
220 }
221 }
222
223 pub fn try_into_eip7702(self) -> Result<TxEip7702, ValueError<Self>> {
225 match self {
226 Self::Eip7702(tx) => Ok(tx),
227 _ => Err(ValueError::new(self, "Expected EIP-7702 transaction")),
228 }
229 }
230
231 pub fn tx_hash(&self, signature: &Signature) -> TxHash {
233 match self {
234 Self::Legacy(tx) => tx.tx_hash(signature),
235 Self::Eip2930(tx) => tx.tx_hash(signature),
236 Self::Eip1559(tx) => tx.tx_hash(signature),
237 Self::Eip4844(tx) => tx.tx_hash(signature),
238 Self::Eip7702(tx) => tx.tx_hash(signature),
239 }
240 }
241}
242
243impl<Eip4844: RlpEcdsaEncodableTx + Typed2718> RlpEcdsaEncodableTx
244 for EthereumTypedTransaction<Eip4844>
245{
246 fn rlp_encoded_fields_length(&self) -> usize {
247 match self {
248 Self::Legacy(tx) => tx.rlp_encoded_fields_length(),
249 Self::Eip2930(tx) => tx.rlp_encoded_fields_length(),
250 Self::Eip1559(tx) => tx.rlp_encoded_fields_length(),
251 Self::Eip4844(tx) => tx.rlp_encoded_fields_length(),
252 Self::Eip7702(tx) => tx.rlp_encoded_fields_length(),
253 }
254 }
255
256 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
257 match self {
258 Self::Legacy(tx) => tx.rlp_encode_fields(out),
259 Self::Eip2930(tx) => tx.rlp_encode_fields(out),
260 Self::Eip1559(tx) => tx.rlp_encode_fields(out),
261 Self::Eip4844(tx) => tx.rlp_encode_fields(out),
262 Self::Eip7702(tx) => tx.rlp_encode_fields(out),
263 }
264 }
265
266 fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
267 match self {
268 Self::Legacy(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
269 Self::Eip2930(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
270 Self::Eip1559(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
271 Self::Eip4844(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
272 Self::Eip7702(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
273 }
274 }
275
276 fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
277 match self {
278 Self::Legacy(tx) => tx.eip2718_encode(signature, out),
279 Self::Eip2930(tx) => tx.eip2718_encode(signature, out),
280 Self::Eip1559(tx) => tx.eip2718_encode(signature, out),
281 Self::Eip4844(tx) => tx.eip2718_encode(signature, out),
282 Self::Eip7702(tx) => tx.eip2718_encode(signature, out),
283 }
284 }
285
286 fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
287 match self {
288 Self::Legacy(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
289 Self::Eip2930(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
290 Self::Eip1559(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
291 Self::Eip4844(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
292 Self::Eip7702(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
293 }
294 }
295
296 fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
297 match self {
298 Self::Legacy(tx) => tx.network_encode(signature, out),
299 Self::Eip2930(tx) => tx.network_encode(signature, out),
300 Self::Eip1559(tx) => tx.network_encode(signature, out),
301 Self::Eip4844(tx) => tx.network_encode(signature, out),
302 Self::Eip7702(tx) => tx.network_encode(signature, out),
303 }
304 }
305
306 fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
307 match self {
308 Self::Legacy(tx) => tx.tx_hash_with_type(signature, tx.ty()),
309 Self::Eip2930(tx) => tx.tx_hash_with_type(signature, tx.ty()),
310 Self::Eip1559(tx) => tx.tx_hash_with_type(signature, tx.ty()),
311 Self::Eip4844(tx) => tx.tx_hash_with_type(signature, tx.ty()),
312 Self::Eip7702(tx) => tx.tx_hash_with_type(signature, tx.ty()),
313 }
314 }
315
316 fn tx_hash(&self, signature: &Signature) -> TxHash {
317 match self {
318 Self::Legacy(tx) => tx.tx_hash(signature),
319 Self::Eip2930(tx) => tx.tx_hash(signature),
320 Self::Eip1559(tx) => tx.tx_hash(signature),
321 Self::Eip4844(tx) => tx.tx_hash(signature),
322 Self::Eip7702(tx) => tx.tx_hash(signature),
323 }
324 }
325}
326
327impl<Eip4844: SignableTransaction<Signature>> SignableTransaction<Signature>
328 for EthereumTypedTransaction<Eip4844>
329{
330 fn set_chain_id(&mut self, chain_id: ChainId) {
331 match self {
332 Self::Legacy(tx) => tx.set_chain_id(chain_id),
333 Self::Eip2930(tx) => tx.set_chain_id(chain_id),
334 Self::Eip1559(tx) => tx.set_chain_id(chain_id),
335 Self::Eip4844(tx) => tx.set_chain_id(chain_id),
336 Self::Eip7702(tx) => tx.set_chain_id(chain_id),
337 }
338 }
339
340 fn encode_for_signing(&self, out: &mut dyn BufMut) {
341 match self {
342 Self::Legacy(tx) => tx.encode_for_signing(out),
343 Self::Eip2930(tx) => tx.encode_for_signing(out),
344 Self::Eip1559(tx) => tx.encode_for_signing(out),
345 Self::Eip4844(tx) => tx.encode_for_signing(out),
346 Self::Eip7702(tx) => tx.encode_for_signing(out),
347 }
348 }
349
350 fn payload_len_for_signature(&self) -> usize {
351 match self {
352 Self::Legacy(tx) => tx.payload_len_for_signature(),
353 Self::Eip2930(tx) => tx.payload_len_for_signature(),
354 Self::Eip1559(tx) => tx.payload_len_for_signature(),
355 Self::Eip4844(tx) => tx.payload_len_for_signature(),
356 Self::Eip7702(tx) => tx.payload_len_for_signature(),
357 }
358 }
359}
360
361#[cfg(feature = "serde")]
362impl<Eip4844, T: From<EthereumTypedTransaction<Eip4844>>> From<EthereumTypedTransaction<Eip4844>>
363 for alloy_serde::WithOtherFields<T>
364{
365 fn from(value: EthereumTypedTransaction<Eip4844>) -> Self {
366 Self::new(value.into())
367 }
368}
369
370#[cfg(feature = "serde")]
371impl<Eip4844, T> From<EthereumTxEnvelope<Eip4844>> for alloy_serde::WithOtherFields<T>
372where
373 T: From<EthereumTxEnvelope<Eip4844>>,
374{
375 fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
376 Self::new(value.into())
377 }
378}
379
380#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
382pub(crate) mod serde_bincode_compat {
383 use alloc::borrow::Cow;
384 use serde::{Deserialize, Deserializer, Serialize, Serializer};
385 use serde_with::{DeserializeAs, SerializeAs};
386
387 #[derive(Debug, Serialize, Deserialize)]
403 pub enum EthereumTypedTransaction<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
404 Legacy(crate::serde_bincode_compat::transaction::TxLegacy<'a>),
406 Eip2930(crate::serde_bincode_compat::transaction::TxEip2930<'a>),
408 Eip1559(crate::serde_bincode_compat::transaction::TxEip1559<'a>),
410 Eip4844(Cow<'a, Eip4844>),
414 Eip7702(crate::serde_bincode_compat::transaction::TxEip7702<'a>),
416 }
417
418 impl<'a, T: Clone> From<&'a super::EthereumTypedTransaction<T>>
419 for EthereumTypedTransaction<'a, T>
420 {
421 fn from(value: &'a super::EthereumTypedTransaction<T>) -> Self {
422 match value {
423 super::EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
424 super::EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
425 super::EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
426 super::EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
427 super::EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
428 }
429 }
430 }
431
432 impl<'a, T: Clone> From<EthereumTypedTransaction<'a, T>> for super::EthereumTypedTransaction<T> {
433 fn from(value: EthereumTypedTransaction<'a, T>) -> Self {
434 match value {
435 EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
436 EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
437 EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
438 EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
439 EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
440 }
441 }
442 }
443
444 impl<T: Serialize + Clone> SerializeAs<super::EthereumTypedTransaction<T>>
445 for EthereumTypedTransaction<'_, T>
446 {
447 fn serialize_as<S>(
448 source: &super::EthereumTypedTransaction<T>,
449 serializer: S,
450 ) -> Result<S::Ok, S::Error>
451 where
452 S: Serializer,
453 {
454 EthereumTypedTransaction::<'_, T>::from(source).serialize(serializer)
455 }
456 }
457
458 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTypedTransaction<T>>
459 for EthereumTypedTransaction<'de, T>
460 {
461 fn deserialize_as<D>(
462 deserializer: D,
463 ) -> Result<super::EthereumTypedTransaction<T>, D::Error>
464 where
465 D: Deserializer<'de>,
466 {
467 EthereumTypedTransaction::<'_, T>::deserialize(deserializer).map(Into::into)
468 }
469 }
470
471 #[cfg(test)]
472 mod tests {
473 use super::super::{serde_bincode_compat, EthereumTypedTransaction};
474 use crate::TxEip4844;
475 use arbitrary::Arbitrary;
476 use bincode::config;
477 use rand::Rng;
478 use serde::{Deserialize, Serialize};
479 use serde_with::serde_as;
480
481 #[test]
482 fn test_typed_tx_bincode_roundtrip() {
483 #[serde_as]
484 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
485 struct Data {
486 #[serde_as(as = "serde_bincode_compat::EthereumTypedTransaction<'_>")]
487 transaction: EthereumTypedTransaction<TxEip4844>,
488 }
489
490 let mut bytes = [0u8; 1024];
491 rand::thread_rng().fill(bytes.as_mut_slice());
492 let data = Data {
493 transaction: EthereumTypedTransaction::arbitrary(
494 &mut arbitrary::Unstructured::new(&bytes),
495 )
496 .unwrap(),
497 };
498
499 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
500 let (decoded, _) =
501 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
502 assert_eq!(decoded, data);
503 }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use alloy_eips::eip2930::AccessList;
511 use alloy_primitives::{Address, Bytes, U256};
512
513 #[test]
514 fn test_decode_unsigned_all_types() {
515 let transactions = [
516 TypedTransaction::Legacy(TxLegacy {
517 chain_id: Some(1),
518 nonce: 0,
519 gas_price: 1,
520 gas_limit: 2,
521 to: Address::ZERO.into(),
522 value: U256::ZERO,
523 input: Bytes::default(),
524 }),
525 TypedTransaction::Eip2930(TxEip2930 {
526 chain_id: 1,
527 nonce: 0,
528 gas_price: 1,
529 gas_limit: 2,
530 to: Address::ZERO.into(),
531 value: U256::ZERO,
532 input: Bytes::default(),
533 access_list: AccessList::default(),
534 }),
535 TypedTransaction::Eip1559(TxEip1559 {
536 chain_id: 1,
537 nonce: 0,
538 gas_limit: 1,
539 max_fee_per_gas: 2,
540 max_priority_fee_per_gas: 3,
541 to: Address::ZERO.into(),
542 value: U256::ZERO,
543 input: Bytes::default(),
544 access_list: AccessList::default(),
545 }),
546 TypedTransaction::Eip7702(TxEip7702 {
547 chain_id: 1,
548 nonce: 0,
549 gas_limit: 1,
550 max_fee_per_gas: 2,
551 max_priority_fee_per_gas: 3,
552 to: Address::ZERO,
553 value: U256::ZERO,
554 input: Bytes::default(),
555 access_list: AccessList::default(),
556 authorization_list: vec![],
557 }),
558 ];
559
560 for tx in transactions {
561 let mut encoded = Vec::new();
562 tx.encode_for_signing(&mut encoded);
563 let decoded = TypedTransaction::decode_unsigned(&mut encoded.as_slice()).unwrap();
564 assert_eq!(decoded, tx);
565 }
566 }
567
568 #[test]
569 fn test_decode_unsigned_invalid_type() {
570 let invalid = vec![0x99, 0xc0];
571 let result = TypedTransaction::decode_unsigned(&mut invalid.as_slice());
572 assert!(result.is_err());
573 if let Err(err) = result {
574 assert!(matches!(err, alloy_eips::eip2718::Eip2718Error::UnexpectedType(0x99)));
575 }
576 }
577
578 #[test]
579 fn test_decode_unsigned_encode_stability() {
580 let tx = TypedTransaction::Eip1559(TxEip1559 {
582 chain_id: 1,
583 nonce: 100,
584 gas_limit: 50000,
585 max_fee_per_gas: 30_000_000_000,
586 max_priority_fee_per_gas: 2_000_000_000,
587 to: Address::random().into(),
588 value: U256::from(1_000_000),
589 input: Bytes::from(vec![1, 2, 3]),
590 access_list: AccessList::default(),
591 });
592
593 let mut encoded = Vec::new();
595 tx.encode_for_signing(&mut encoded);
596
597 let decoded = TypedTransaction::decode_unsigned(&mut encoded.as_slice()).unwrap();
599 assert_eq!(decoded, tx);
600
601 let mut re_encoded = Vec::new();
603 decoded.encode_for_signing(&mut re_encoded);
604
605 assert_eq!(encoded, re_encoded);
606 }
607}