1use alloy_consensus::{
3 Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930,
4 TxEnvelope, TxLegacy, TxReceipt, Typed2718,
5 transaction::{
6 Recovered, TxEip7702,
7 eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
8 },
9};
10use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
11use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope};
12use alloy_primitives::{Address, B256, Bloom, Bytes, Log, Signature, TxHash, TxKind, U64, U256};
13use alloy_rlp::{Decodable, Encodable, Header, length_of_length};
14use alloy_rpc_types::{
15 AccessList, ConversionError, Transaction as RpcTransaction, TransactionReceipt,
16 request::TransactionRequest, trace::otterscan::OtsReceipt,
17};
18use alloy_serde::{OtherFields, WithOtherFields};
19use bytes::BufMut;
20use foundry_evm::traces::CallTraceNode;
21use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit};
22use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
23use revm::{context::TxEnv, interpreter::InstructionResult};
24use serde::{Deserialize, Serialize};
25use std::ops::{Deref, Mul};
26
27pub fn transaction_request_to_typed(
30 tx: WithOtherFields<TransactionRequest>,
31) -> Option<TypedTransactionRequest> {
32 let WithOtherFields::<TransactionRequest> {
33 inner:
34 TransactionRequest {
35 from,
36 to,
37 gas_price,
38 max_fee_per_gas,
39 max_priority_fee_per_gas,
40 max_fee_per_blob_gas,
41 blob_versioned_hashes,
42 gas,
43 value,
44 input,
45 nonce,
46 access_list,
47 sidecar,
48 transaction_type,
49 authorization_list,
50 chain_id: _,
51 },
52 other,
53 } = tx;
54
55 if transaction_type == Some(0x7E) || has_optimism_fields(&other) {
57 let mint = other.get_deserialized::<U256>("mint")?.map(|m| m.to::<u128>()).ok()?;
58
59 return Some(TypedTransactionRequest::Deposit(TxDeposit {
60 from: from.unwrap_or_default(),
61 source_hash: other.get_deserialized::<B256>("sourceHash")?.ok()?,
62 to: to.unwrap_or_default(),
63 mint,
64 value: value.unwrap_or_default(),
65 gas_limit: gas.unwrap_or_default(),
66 is_system_transaction: other.get_deserialized::<bool>("isSystemTx")?.ok()?,
67 input: input.into_input().unwrap_or_default(),
68 }));
69 }
70
71 if transaction_type == Some(4) || authorization_list.is_some() {
73 return Some(TypedTransactionRequest::EIP7702(TxEip7702 {
74 nonce: nonce.unwrap_or_default(),
75 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
76 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
77 gas_limit: gas.unwrap_or_default(),
78 value: value.unwrap_or(U256::ZERO),
79 input: input.into_input().unwrap_or_default(),
80 to: to?.into_to()?,
82 chain_id: 0,
83 access_list: access_list.unwrap_or_default(),
84 authorization_list: authorization_list.unwrap_or_default(),
85 }));
86 }
87
88 match (
89 transaction_type,
90 gas_price,
91 max_fee_per_gas,
92 max_priority_fee_per_gas,
93 access_list.as_ref(),
94 max_fee_per_blob_gas,
95 blob_versioned_hashes.as_ref(),
96 sidecar.as_ref(),
97 to,
98 ) {
99 (Some(0), _, None, None, None, None, None, None, _)
101 | (None, Some(_), None, None, None, None, None, None, _) => {
102 Some(TypedTransactionRequest::Legacy(TxLegacy {
103 nonce: nonce.unwrap_or_default(),
104 gas_price: gas_price.unwrap_or_default(),
105 gas_limit: gas.unwrap_or_default(),
106 value: value.unwrap_or(U256::ZERO),
107 input: input.into_input().unwrap_or_default(),
108 to: to.unwrap_or_default(),
109 chain_id: None,
110 }))
111 }
112 (Some(1), _, None, None, _, None, None, None, _)
114 | (None, _, None, None, Some(_), None, None, None, _) => {
115 Some(TypedTransactionRequest::EIP2930(TxEip2930 {
116 nonce: nonce.unwrap_or_default(),
117 gas_price: gas_price.unwrap_or_default(),
118 gas_limit: gas.unwrap_or_default(),
119 value: value.unwrap_or(U256::ZERO),
120 input: input.into_input().unwrap_or_default(),
121 to: to.unwrap_or_default(),
122 chain_id: 0,
123 access_list: access_list.unwrap_or_default(),
124 }))
125 }
126 (Some(2), None, _, _, _, _, None, None, _)
128 | (None, None, Some(_), _, _, _, None, None, _)
129 | (None, None, _, Some(_), _, _, None, None, _)
130 | (None, None, None, None, None, _, None, None, _) => {
131 Some(TypedTransactionRequest::EIP1559(TxEip1559 {
133 nonce: nonce.unwrap_or_default(),
134 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
135 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
136 gas_limit: gas.unwrap_or_default(),
137 value: value.unwrap_or(U256::ZERO),
138 input: input.into_input().unwrap_or_default(),
139 to: to.unwrap_or_default(),
140 chain_id: 0,
141 access_list: access_list.unwrap_or_default(),
142 }))
143 }
144 (Some(3), None, _, _, _, _, Some(_), _, to) => {
146 let tx = TxEip4844 {
147 nonce: nonce.unwrap_or_default(),
148 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
149 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
150 max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(),
151 gas_limit: gas.unwrap_or_default(),
152 value: value.unwrap_or(U256::ZERO),
153 input: input.into_input().unwrap_or_default(),
154 to: match to.unwrap_or(TxKind::Create) {
155 TxKind::Call(to) => to,
156 TxKind::Create => Address::ZERO,
157 },
158 chain_id: 0,
159 access_list: access_list.unwrap_or_default(),
160 blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(),
161 };
162
163 if let Some(sidecar) = sidecar {
164 Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar(
165 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar),
166 )))
167 } else {
168 Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx)))
169 }
170 }
171 _ => None,
172 }
173}
174
175pub fn has_optimism_fields(other: &OtherFields) -> bool {
176 other.contains_key("sourceHash")
177 && other.contains_key("mint")
178 && other.contains_key("isSystemTx")
179}
180
181#[derive(Clone, Debug, PartialEq, Eq)]
182pub enum TypedTransactionRequest {
183 Legacy(TxLegacy),
184 EIP2930(TxEip2930),
185 EIP1559(TxEip1559),
186 EIP7702(TxEip7702),
187 EIP4844(TxEip4844Variant),
188 Deposit(TxDeposit),
189}
190
191#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
196pub struct MaybeImpersonatedTransaction {
197 pub transaction: TypedTransaction,
198 pub impersonated_sender: Option<Address>,
199}
200
201impl MaybeImpersonatedTransaction {
202 pub fn new(transaction: TypedTransaction) -> Self {
204 Self { transaction, impersonated_sender: None }
205 }
206
207 pub fn impersonated(transaction: TypedTransaction, impersonated_sender: Address) -> Self {
209 Self { transaction, impersonated_sender: Some(impersonated_sender) }
210 }
211
212 pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
214 if let Some(sender) = self.impersonated_sender {
215 return Ok(sender);
216 }
217 self.transaction.recover()
218 }
219
220 pub fn is_impersonated(&self) -> bool {
222 self.impersonated_sender.is_some()
223 }
224
225 pub fn hash(&self) -> B256 {
227 if let Some(sender) = self.impersonated_sender {
228 return self.transaction.impersonated_hash(sender);
229 }
230 self.transaction.hash()
231 }
232}
233
234impl Encodable for MaybeImpersonatedTransaction {
235 fn encode(&self, out: &mut dyn bytes::BufMut) {
236 self.transaction.encode(out)
237 }
238}
239
240impl From<MaybeImpersonatedTransaction> for TypedTransaction {
241 fn from(value: MaybeImpersonatedTransaction) -> Self {
242 value.transaction
243 }
244}
245
246impl From<TypedTransaction> for MaybeImpersonatedTransaction {
247 fn from(value: TypedTransaction) -> Self {
248 Self::new(value)
249 }
250}
251
252impl Decodable for MaybeImpersonatedTransaction {
253 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
254 TypedTransaction::decode(buf).map(Self::new)
255 }
256}
257
258impl AsRef<TypedTransaction> for MaybeImpersonatedTransaction {
259 fn as_ref(&self) -> &TypedTransaction {
260 &self.transaction
261 }
262}
263
264impl Deref for MaybeImpersonatedTransaction {
265 type Target = TypedTransaction;
266
267 fn deref(&self) -> &Self::Target {
268 &self.transaction
269 }
270}
271
272impl From<MaybeImpersonatedTransaction> for RpcTransaction {
273 fn from(value: MaybeImpersonatedTransaction) -> Self {
274 let hash = value.hash();
275 let sender = value.recover().unwrap_or_default();
276 to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender)
277 }
278}
279
280pub fn to_alloy_transaction_with_hash_and_sender(
281 transaction: TypedTransaction,
282 hash: B256,
283 from: Address,
284) -> RpcTransaction {
285 match transaction {
286 TypedTransaction::Legacy(t) => {
287 let (tx, sig, _) = t.into_parts();
288 RpcTransaction {
289 block_hash: None,
290 block_number: None,
291 transaction_index: None,
292 effective_gas_price: None,
293 inner: Recovered::new_unchecked(
294 TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)),
295 from,
296 ),
297 }
298 }
299 TypedTransaction::EIP2930(t) => {
300 let (tx, sig, _) = t.into_parts();
301 RpcTransaction {
302 block_hash: None,
303 block_number: None,
304 transaction_index: None,
305 effective_gas_price: None,
306 inner: Recovered::new_unchecked(
307 TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)),
308 from,
309 ),
310 }
311 }
312 TypedTransaction::EIP1559(t) => {
313 let (tx, sig, _) = t.into_parts();
314 RpcTransaction {
315 block_hash: None,
316 block_number: None,
317 transaction_index: None,
318 effective_gas_price: None,
319 inner: Recovered::new_unchecked(
320 TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)),
321 from,
322 ),
323 }
324 }
325 TypedTransaction::EIP4844(t) => {
326 let (tx, sig, _) = t.into_parts();
327 RpcTransaction {
328 block_hash: None,
329 block_number: None,
330 transaction_index: None,
331 effective_gas_price: None,
332 inner: Recovered::new_unchecked(
333 TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)),
334 from,
335 ),
336 }
337 }
338 TypedTransaction::EIP7702(t) => {
339 let (tx, sig, _) = t.into_parts();
340 RpcTransaction {
341 block_hash: None,
342 block_number: None,
343 transaction_index: None,
344 effective_gas_price: None,
345 inner: Recovered::new_unchecked(
346 TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)),
347 from,
348 ),
349 }
350 }
351 TypedTransaction::Deposit(_t) => {
352 unreachable!("cannot reach here, handled in `transaction_build` ")
353 }
354 }
355}
356
357#[derive(Clone, Debug, PartialEq, Eq)]
359pub struct PendingTransaction {
360 pub transaction: MaybeImpersonatedTransaction,
362 sender: Address,
364 hash: TxHash,
366}
367
368impl PendingTransaction {
369 pub fn new(transaction: TypedTransaction) -> Result<Self, alloy_primitives::SignatureError> {
370 let sender = transaction.recover()?;
371 let hash = transaction.hash();
372 Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
373 }
374
375 pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self {
376 let hash = transaction.impersonated_hash(sender);
377 Self {
378 transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
379 sender,
380 hash,
381 }
382 }
383
384 pub fn from_maybe_impersonated(
386 transaction: MaybeImpersonatedTransaction,
387 ) -> Result<Self, alloy_primitives::SignatureError> {
388 if let Some(impersonated) = transaction.impersonated_sender {
389 Ok(Self::with_impersonated(transaction.transaction, impersonated))
390 } else {
391 Self::new(transaction.transaction)
392 }
393 }
394
395 pub fn nonce(&self) -> u64 {
396 self.transaction.nonce()
397 }
398
399 pub fn hash(&self) -> &TxHash {
400 &self.hash
401 }
402
403 pub fn sender(&self) -> &Address {
404 &self.sender
405 }
406
407 pub fn to_revm_tx_env(&self) -> OpTransaction<TxEnv> {
412 fn transact_to(kind: &TxKind) -> TxKind {
413 match kind {
414 TxKind::Call(c) => TxKind::Call(*c),
415 TxKind::Create => TxKind::Create,
416 }
417 }
418
419 let caller = *self.sender();
420 match &self.transaction.transaction {
421 TypedTransaction::Legacy(tx) => {
422 let chain_id = tx.tx().chain_id;
423 let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx();
424 OpTransaction::new(TxEnv {
425 caller,
426 kind: transact_to(to),
427 data: input.clone(),
428 chain_id,
429 nonce: *nonce,
430 value: (*value),
431 gas_price: *gas_price,
432 gas_priority_fee: None,
433 gas_limit: *gas_limit,
434 access_list: vec![].into(),
435 tx_type: 0,
436 ..Default::default()
437 })
438 }
439 TypedTransaction::EIP2930(tx) => {
440 let TxEip2930 {
441 chain_id,
442 nonce,
443 gas_price,
444 gas_limit,
445 to,
446 value,
447 input,
448 access_list,
449 ..
450 } = tx.tx();
451 OpTransaction::new(TxEnv {
452 caller,
453 kind: transact_to(to),
454 data: input.clone(),
455 chain_id: Some(*chain_id),
456 nonce: *nonce,
457 value: *value,
458 gas_price: *gas_price,
459 gas_priority_fee: None,
460 gas_limit: *gas_limit,
461 access_list: access_list.clone(),
462 tx_type: 1,
463 ..Default::default()
464 })
465 }
466 TypedTransaction::EIP1559(tx) => {
467 let TxEip1559 {
468 chain_id,
469 nonce,
470 max_priority_fee_per_gas,
471 max_fee_per_gas,
472 gas_limit,
473 to,
474 value,
475 input,
476 access_list,
477 ..
478 } = tx.tx();
479 OpTransaction::new(TxEnv {
480 caller,
481 kind: transact_to(to),
482 data: input.clone(),
483 chain_id: Some(*chain_id),
484 nonce: *nonce,
485 value: *value,
486 gas_price: *max_fee_per_gas,
487 gas_priority_fee: Some(*max_priority_fee_per_gas),
488 gas_limit: *gas_limit,
489 access_list: access_list.clone(),
490 tx_type: 2,
491 ..Default::default()
492 })
493 }
494 TypedTransaction::EIP4844(tx) => {
495 let TxEip4844 {
496 chain_id,
497 nonce,
498 max_fee_per_blob_gas,
499 max_fee_per_gas,
500 max_priority_fee_per_gas,
501 gas_limit,
502 to,
503 value,
504 input,
505 access_list,
506 blob_versioned_hashes,
507 ..
508 } = tx.tx().tx();
509 OpTransaction::new(TxEnv {
510 caller,
511 kind: TxKind::Call(*to),
512 data: input.clone(),
513 chain_id: Some(*chain_id),
514 nonce: *nonce,
515 value: *value,
516 gas_price: *max_fee_per_gas,
517 gas_priority_fee: Some(*max_priority_fee_per_gas),
518 max_fee_per_blob_gas: *max_fee_per_blob_gas,
519 blob_hashes: blob_versioned_hashes.clone(),
520 gas_limit: *gas_limit,
521 access_list: access_list.clone(),
522 tx_type: 3,
523 ..Default::default()
524 })
525 }
526 TypedTransaction::EIP7702(tx) => {
527 let TxEip7702 {
528 chain_id,
529 nonce,
530 gas_limit,
531 max_fee_per_gas,
532 max_priority_fee_per_gas,
533 to,
534 value,
535 access_list,
536 authorization_list,
537 input,
538 } = tx.tx();
539
540 let mut tx = TxEnv {
541 caller,
542 kind: TxKind::Call(*to),
543 data: input.clone(),
544 chain_id: Some(*chain_id),
545 nonce: *nonce,
546 value: *value,
547 gas_price: *max_fee_per_gas,
548 gas_priority_fee: Some(*max_priority_fee_per_gas),
549 gas_limit: *gas_limit,
550 access_list: access_list.clone(),
551 tx_type: 4,
552 ..Default::default()
553 };
554 tx.set_signed_authorization(authorization_list.clone());
555
556 OpTransaction::new(tx)
557 }
558 TypedTransaction::Deposit(tx) => {
559 let chain_id = tx.chain_id();
560 let TxDeposit {
561 source_hash,
562 to,
563 mint,
564 value,
565 gas_limit,
566 is_system_transaction,
567 input,
568 ..
569 } = tx;
570
571 let base = TxEnv {
572 caller,
573 kind: transact_to(to),
574 data: input.clone(),
575 chain_id,
576 nonce: 0,
577 value: *value,
578 gas_price: 0,
579 gas_priority_fee: None,
580 gas_limit: { *gas_limit },
581 access_list: vec![].into(),
582 tx_type: DEPOSIT_TX_TYPE_ID,
583 ..Default::default()
584 };
585
586 let deposit = DepositTransactionParts {
587 source_hash: *source_hash,
588 mint: Some(*mint),
589 is_system_transaction: *is_system_transaction,
590 };
591
592 OpTransaction { base, deposit, enveloped_tx: None }
593 }
594 }
595 }
596}
597
598#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
600pub enum TypedTransaction {
601 Legacy(Signed<TxLegacy>),
603 EIP2930(Signed<TxEip2930>),
605 EIP1559(Signed<TxEip1559>),
607 EIP4844(Signed<TxEip4844Variant>),
609 EIP7702(Signed<TxEip7702>),
611 Deposit(TxDeposit),
613}
614
615impl TryFrom<AnyRpcTransaction> for TypedTransaction {
616 type Error = ConversionError;
617
618 fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
619 let WithOtherFields { inner, .. } = value.0;
620 let from = inner.inner.signer();
621 match inner.inner.into_inner() {
622 AnyTxEnvelope::Ethereum(tx) => match tx {
623 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
624 TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
625 TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
626 TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
627 TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
628 },
629 AnyTxEnvelope::Unknown(mut tx) => {
630 if tx.ty() == DEPOSIT_TX_TYPE_ID {
632 tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap());
633 let deposit_tx =
634 tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
635 ConversionError::Custom(format!(
636 "Failed to deserialize deposit tx: {e}"
637 ))
638 })?;
639
640 return Ok(Self::Deposit(deposit_tx));
641 };
642
643 Err(ConversionError::Custom("UnknownTxType".to_string()))
644 }
645 }
646 }
647}
648
649impl TypedTransaction {
650 pub fn is_dynamic_fee(&self) -> bool {
652 matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_))
653 }
654
655 pub fn gas_price(&self) -> u128 {
656 match self {
657 Self::Legacy(tx) => tx.tx().gas_price,
658 Self::EIP2930(tx) => tx.tx().gas_price,
659 Self::EIP1559(tx) => tx.tx().max_fee_per_gas,
660 Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas,
661 Self::EIP7702(tx) => tx.tx().max_fee_per_gas,
662 Self::Deposit(_) => 0,
663 }
664 }
665
666 pub fn gas_limit(&self) -> u64 {
667 match self {
668 Self::Legacy(tx) => tx.tx().gas_limit,
669 Self::EIP2930(tx) => tx.tx().gas_limit,
670 Self::EIP1559(tx) => tx.tx().gas_limit,
671 Self::EIP4844(tx) => tx.tx().tx().gas_limit,
672 Self::EIP7702(tx) => tx.tx().gas_limit,
673 Self::Deposit(tx) => tx.gas_limit,
674 }
675 }
676
677 pub fn value(&self) -> U256 {
678 U256::from(match self {
679 Self::Legacy(tx) => tx.tx().value,
680 Self::EIP2930(tx) => tx.tx().value,
681 Self::EIP1559(tx) => tx.tx().value,
682 Self::EIP4844(tx) => tx.tx().tx().value,
683 Self::EIP7702(tx) => tx.tx().value,
684 Self::Deposit(tx) => tx.value,
685 })
686 }
687
688 pub fn data(&self) -> &Bytes {
689 match self {
690 Self::Legacy(tx) => &tx.tx().input,
691 Self::EIP2930(tx) => &tx.tx().input,
692 Self::EIP1559(tx) => &tx.tx().input,
693 Self::EIP4844(tx) => &tx.tx().tx().input,
694 Self::EIP7702(tx) => &tx.tx().input,
695 Self::Deposit(tx) => &tx.input,
696 }
697 }
698
699 pub fn r#type(&self) -> Option<u8> {
701 match self {
702 Self::Legacy(_) => None,
703 Self::EIP2930(_) => Some(1),
704 Self::EIP1559(_) => Some(2),
705 Self::EIP4844(_) => Some(3),
706 Self::EIP7702(_) => Some(4),
707 Self::Deposit(_) => Some(0x7E),
708 }
709 }
710
711 pub fn max_cost(&self) -> u128 {
716 let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price());
717
718 if self.is_eip4844() {
719 max_cost = max_cost.saturating_add(
720 self.blob_gas()
721 .map(|g| g as u128)
722 .unwrap_or(0)
723 .mul(self.max_fee_per_blob_gas().unwrap_or(0)),
724 )
725 }
726
727 max_cost
728 }
729
730 pub fn blob_gas(&self) -> Option<u64> {
731 match self {
732 Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()),
733 _ => None,
734 }
735 }
736
737 pub fn sidecar(&self) -> Option<&TxEip4844WithSidecar> {
738 match self {
739 Self::EIP4844(signed_variant) => match signed_variant.tx() {
740 TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar),
741 _ => None,
742 },
743 _ => None,
744 }
745 }
746
747 pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
748 match self {
749 Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas),
750 _ => None,
751 }
752 }
753
754 pub fn essentials(&self) -> TransactionEssentials {
756 match self {
757 Self::Legacy(t) => TransactionEssentials {
758 kind: t.tx().to,
759 input: t.tx().input.clone(),
760 nonce: t.tx().nonce,
761 gas_limit: t.tx().gas_limit,
762 gas_price: Some(t.tx().gas_price),
763 max_fee_per_gas: None,
764 max_priority_fee_per_gas: None,
765 max_fee_per_blob_gas: None,
766 blob_versioned_hashes: None,
767 value: t.tx().value,
768 chain_id: t.tx().chain_id,
769 access_list: Default::default(),
770 },
771 Self::EIP2930(t) => TransactionEssentials {
772 kind: t.tx().to,
773 input: t.tx().input.clone(),
774 nonce: t.tx().nonce,
775 gas_limit: t.tx().gas_limit,
776 gas_price: Some(t.tx().gas_price),
777 max_fee_per_gas: None,
778 max_priority_fee_per_gas: None,
779 max_fee_per_blob_gas: None,
780 blob_versioned_hashes: None,
781 value: t.tx().value,
782 chain_id: Some(t.tx().chain_id),
783 access_list: t.tx().access_list.clone(),
784 },
785 Self::EIP1559(t) => TransactionEssentials {
786 kind: t.tx().to,
787 input: t.tx().input.clone(),
788 nonce: t.tx().nonce,
789 gas_limit: t.tx().gas_limit,
790 gas_price: None,
791 max_fee_per_gas: Some(t.tx().max_fee_per_gas),
792 max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
793 max_fee_per_blob_gas: None,
794 blob_versioned_hashes: None,
795 value: t.tx().value,
796 chain_id: Some(t.tx().chain_id),
797 access_list: t.tx().access_list.clone(),
798 },
799 Self::EIP4844(t) => TransactionEssentials {
800 kind: TxKind::Call(t.tx().tx().to),
801 input: t.tx().tx().input.clone(),
802 nonce: t.tx().tx().nonce,
803 gas_limit: t.tx().tx().gas_limit,
804 gas_price: None,
805 max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas),
806 max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas),
807 max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas),
808 blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()),
809 value: t.tx().tx().value,
810 chain_id: Some(t.tx().tx().chain_id),
811 access_list: t.tx().tx().access_list.clone(),
812 },
813 Self::EIP7702(t) => TransactionEssentials {
814 kind: TxKind::Call(t.tx().to),
815 input: t.tx().input.clone(),
816 nonce: t.tx().nonce,
817 gas_limit: t.tx().gas_limit,
818 gas_price: None,
819 max_fee_per_gas: Some(t.tx().max_fee_per_gas),
820 max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
821 max_fee_per_blob_gas: None,
822 blob_versioned_hashes: None,
823 value: t.tx().value,
824 chain_id: Some(t.tx().chain_id),
825 access_list: t.tx().access_list.clone(),
826 },
827 Self::Deposit(t) => TransactionEssentials {
828 kind: t.to,
829 input: t.input.clone(),
830 nonce: 0,
831 gas_limit: t.gas_limit,
832 gas_price: Some(0),
833 max_fee_per_gas: None,
834 max_priority_fee_per_gas: None,
835 max_fee_per_blob_gas: None,
836 blob_versioned_hashes: None,
837 value: t.value,
838 chain_id: t.chain_id(),
839 access_list: Default::default(),
840 },
841 }
842 }
843
844 pub fn nonce(&self) -> u64 {
845 match self {
846 Self::Legacy(t) => t.tx().nonce,
847 Self::EIP2930(t) => t.tx().nonce,
848 Self::EIP1559(t) => t.tx().nonce,
849 Self::EIP4844(t) => t.tx().tx().nonce,
850 Self::EIP7702(t) => t.tx().nonce,
851 Self::Deposit(_t) => 0,
852 }
853 }
854
855 pub fn chain_id(&self) -> Option<u64> {
856 match self {
857 Self::Legacy(t) => t.tx().chain_id,
858 Self::EIP2930(t) => Some(t.tx().chain_id),
859 Self::EIP1559(t) => Some(t.tx().chain_id),
860 Self::EIP4844(t) => Some(t.tx().tx().chain_id),
861 Self::EIP7702(t) => Some(t.tx().chain_id),
862 Self::Deposit(t) => t.chain_id(),
863 }
864 }
865
866 pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
867 match self {
868 Self::Legacy(tx) => Some(tx),
869 _ => None,
870 }
871 }
872
873 pub fn is_legacy(&self) -> bool {
875 matches!(self, Self::Legacy(_))
876 }
877
878 pub fn is_eip1559(&self) -> bool {
880 matches!(self, Self::EIP1559(_))
881 }
882
883 pub fn is_eip2930(&self) -> bool {
885 matches!(self, Self::EIP2930(_))
886 }
887
888 pub fn is_eip4844(&self) -> bool {
890 matches!(self, Self::EIP4844(_))
891 }
892
893 pub fn is_eip7702(&self) -> bool {
895 matches!(self, Self::EIP7702(_))
896 }
897
898 pub fn hash(&self) -> B256 {
903 match self {
904 Self::Legacy(t) => *t.hash(),
905 Self::EIP2930(t) => *t.hash(),
906 Self::EIP1559(t) => *t.hash(),
907 Self::EIP4844(t) => *t.hash(),
908 Self::EIP7702(t) => *t.hash(),
909 Self::Deposit(t) => t.tx_hash(),
910 }
911 }
912
913 pub fn impersonated_hash(&self, sender: Address) -> B256 {
917 let mut buffer = Vec::new();
918 Encodable::encode(self, &mut buffer);
919 buffer.extend_from_slice(sender.as_ref());
920 B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice())
921 }
922
923 pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
925 match self {
926 Self::Legacy(tx) => tx.recover_signer(),
927 Self::EIP2930(tx) => tx.recover_signer(),
928 Self::EIP1559(tx) => tx.recover_signer(),
929 Self::EIP4844(tx) => tx.recover_signer(),
930 Self::EIP7702(tx) => tx.recover_signer(),
931 Self::Deposit(tx) => Ok(tx.from),
932 }
933 }
934
935 pub fn kind(&self) -> TxKind {
937 match self {
938 Self::Legacy(tx) => tx.tx().to,
939 Self::EIP2930(tx) => tx.tx().to,
940 Self::EIP1559(tx) => tx.tx().to,
941 Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to),
942 Self::EIP7702(tx) => TxKind::Call(tx.tx().to),
943 Self::Deposit(tx) => tx.to,
944 }
945 }
946
947 pub fn to(&self) -> Option<Address> {
949 self.kind().to().copied()
950 }
951
952 pub fn signature(&self) -> Signature {
954 match self {
955 Self::Legacy(tx) => *tx.signature(),
956 Self::EIP2930(tx) => *tx.signature(),
957 Self::EIP1559(tx) => *tx.signature(),
958 Self::EIP4844(tx) => *tx.signature(),
959 Self::EIP7702(tx) => *tx.signature(),
960 Self::Deposit(_) => Signature::from_scalars_and_parity(
961 B256::with_last_byte(1),
962 B256::with_last_byte(1),
963 false,
964 ),
965 }
966 }
967}
968
969impl Encodable for TypedTransaction {
970 fn encode(&self, out: &mut dyn bytes::BufMut) {
971 if !self.is_legacy() {
972 Header { list: false, payload_length: self.encode_2718_len() }.encode(out);
973 }
974
975 self.encode_2718(out);
976 }
977}
978
979impl Decodable for TypedTransaction {
980 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
981 let mut h_decode_copy = *buf;
982 let header = alloy_rlp::Header::decode(&mut h_decode_copy)?;
983
984 if header.list {
986 return Ok(TxEnvelope::decode(buf)?.into());
987 }
988
989 let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?;
991
992 if ty != 0x7E {
993 Ok(TxEnvelope::decode(buf)?.into())
994 } else {
995 Ok(Self::Deposit(TxDeposit::decode_2718(buf)?))
996 }
997 }
998}
999
1000impl Typed2718 for TypedTransaction {
1001 fn ty(&self) -> u8 {
1002 self.r#type().unwrap_or(0)
1003 }
1004}
1005
1006impl Encodable2718 for TypedTransaction {
1007 fn encode_2718_len(&self) -> usize {
1008 match self {
1009 Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1010 Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1011 Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1012 Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1013 Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1014 Self::Deposit(tx) => 1 + tx.length(),
1015 }
1016 }
1017
1018 fn encode_2718(&self, out: &mut dyn BufMut) {
1019 match self {
1020 Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1021 Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1022 Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1023 Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1024 Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1025 Self::Deposit(tx) => {
1026 tx.encode_2718(out);
1027 }
1028 }
1029 }
1030}
1031
1032impl Decodable2718 for TypedTransaction {
1033 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1034 if ty == 0x7E {
1035 return Ok(Self::Deposit(TxDeposit::decode(buf)?));
1036 }
1037 match TxEnvelope::typed_decode(ty, buf)? {
1038 TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1039 TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1040 TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1041 TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
1042 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
1043 }
1044 }
1045
1046 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1047 match TxEnvelope::fallback_decode(buf)? {
1048 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1049 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
1050 }
1051 }
1052}
1053
1054impl From<TxEnvelope> for TypedTransaction {
1055 fn from(value: TxEnvelope) -> Self {
1056 match value {
1057 TxEnvelope::Legacy(tx) => Self::Legacy(tx),
1058 TxEnvelope::Eip2930(tx) => Self::EIP2930(tx),
1059 TxEnvelope::Eip1559(tx) => Self::EIP1559(tx),
1060 TxEnvelope::Eip4844(tx) => Self::EIP4844(tx),
1061 TxEnvelope::Eip7702(tx) => Self::EIP7702(tx),
1062 }
1063 }
1064}
1065
1066#[derive(Clone, Debug, PartialEq, Eq)]
1067pub struct TransactionEssentials {
1068 pub kind: TxKind,
1069 pub input: Bytes,
1070 pub nonce: u64,
1071 pub gas_limit: u64,
1072 pub gas_price: Option<u128>,
1073 pub max_fee_per_gas: Option<u128>,
1074 pub max_priority_fee_per_gas: Option<u128>,
1075 pub max_fee_per_blob_gas: Option<u128>,
1076 pub blob_versioned_hashes: Option<Vec<B256>>,
1077 pub value: U256,
1078 pub chain_id: Option<u64>,
1079 pub access_list: AccessList,
1080}
1081
1082#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1084pub struct TransactionInfo {
1085 pub transaction_hash: B256,
1086 pub transaction_index: u64,
1087 pub from: Address,
1088 pub to: Option<Address>,
1089 pub contract_address: Option<Address>,
1090 pub traces: Vec<CallTraceNode>,
1091 pub exit: InstructionResult,
1092 pub out: Option<Bytes>,
1093 pub nonce: u64,
1094 pub gas_used: u64,
1095}
1096
1097#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1098#[serde(rename_all = "camelCase")]
1099pub struct DepositReceipt<T = Receipt<alloy_primitives::Log>> {
1100 #[serde(flatten)]
1101 pub inner: ReceiptWithBloom<T>,
1102 #[serde(default, with = "alloy_serde::quantity::opt")]
1103 pub deposit_nonce: Option<u64>,
1104 #[serde(default, with = "alloy_serde::quantity::opt")]
1105 pub deposit_receipt_version: Option<u64>,
1106}
1107
1108impl DepositReceipt {
1109 fn payload_len(&self) -> usize {
1110 self.inner.receipt.status.length()
1111 + self.inner.receipt.cumulative_gas_used.length()
1112 + self.inner.logs_bloom.length()
1113 + self.inner.receipt.logs.length()
1114 + self.deposit_nonce.map_or(0, |n| n.length())
1115 + self.deposit_receipt_version.map_or(0, |n| n.length())
1116 }
1117
1118 fn receipt_rlp_header(&self) -> alloy_rlp::Header {
1120 alloy_rlp::Header { list: true, payload_length: self.payload_len() }
1121 }
1122
1123 fn encode_fields(&self, out: &mut dyn BufMut) {
1125 self.receipt_rlp_header().encode(out);
1126 self.inner.status().encode(out);
1127 self.inner.receipt.cumulative_gas_used.encode(out);
1128 self.inner.logs_bloom.encode(out);
1129 self.inner.receipt.logs.encode(out);
1130 if let Some(n) = self.deposit_nonce {
1131 n.encode(out);
1132 }
1133 if let Some(n) = self.deposit_receipt_version {
1134 n.encode(out);
1135 }
1136 }
1137
1138 fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1140 let b: &mut &[u8] = &mut &**buf;
1141 let rlp_head = alloy_rlp::Header::decode(b)?;
1142 if !rlp_head.list {
1143 return Err(alloy_rlp::Error::UnexpectedString);
1144 }
1145 let started_len = b.len();
1146 let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0;
1147
1148 let status = Decodable::decode(b)?;
1149 let cumulative_gas_used = Decodable::decode(b)?;
1150 let logs_bloom = Decodable::decode(b)?;
1151 let logs: Vec<Log> = Decodable::decode(b)?;
1152 let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1153 let deposit_nonce_version =
1154 remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1155
1156 let this = Self {
1157 inner: ReceiptWithBloom {
1158 receipt: Receipt { status, cumulative_gas_used, logs },
1159 logs_bloom,
1160 },
1161 deposit_nonce,
1162 deposit_receipt_version: deposit_nonce_version,
1163 };
1164
1165 let consumed = started_len - b.len();
1166 if consumed != rlp_head.payload_length {
1167 return Err(alloy_rlp::Error::ListLengthMismatch {
1168 expected: rlp_head.payload_length,
1169 got: consumed,
1170 });
1171 }
1172
1173 *buf = *b;
1174 Ok(this)
1175 }
1176}
1177
1178impl alloy_rlp::Encodable for DepositReceipt {
1179 fn encode(&self, out: &mut dyn BufMut) {
1180 self.encode_fields(out);
1181 }
1182
1183 fn length(&self) -> usize {
1184 let payload_length = self.payload_len();
1185 payload_length + length_of_length(payload_length)
1186 }
1187}
1188
1189impl alloy_rlp::Decodable for DepositReceipt {
1190 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1191 Self::decode_receipt(buf)
1192 }
1193}
1194
1195#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1196#[serde(tag = "type")]
1197pub enum TypedReceipt<T = Receipt<alloy_primitives::Log>> {
1198 #[serde(rename = "0x0", alias = "0x00")]
1199 Legacy(ReceiptWithBloom<T>),
1200 #[serde(rename = "0x1", alias = "0x01")]
1201 EIP2930(ReceiptWithBloom<T>),
1202 #[serde(rename = "0x2", alias = "0x02")]
1203 EIP1559(ReceiptWithBloom<T>),
1204 #[serde(rename = "0x3", alias = "0x03")]
1205 EIP4844(ReceiptWithBloom<T>),
1206 #[serde(rename = "0x4", alias = "0x04")]
1207 EIP7702(ReceiptWithBloom<T>),
1208 #[serde(rename = "0x7E", alias = "0x7e")]
1209 Deposit(DepositReceipt<T>),
1210}
1211
1212impl<T> TypedReceipt<T> {
1213 pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<T> {
1214 match self {
1215 Self::Legacy(r)
1216 | Self::EIP1559(r)
1217 | Self::EIP2930(r)
1218 | Self::EIP4844(r)
1219 | Self::EIP7702(r) => r,
1220 Self::Deposit(r) => &r.inner,
1221 }
1222 }
1223}
1224
1225impl<T> From<TypedReceipt<T>> for ReceiptWithBloom<T> {
1226 fn from(value: TypedReceipt<T>) -> Self {
1227 match value {
1228 TypedReceipt::Legacy(r)
1229 | TypedReceipt::EIP1559(r)
1230 | TypedReceipt::EIP2930(r)
1231 | TypedReceipt::EIP4844(r)
1232 | TypedReceipt::EIP7702(r) => r,
1233 TypedReceipt::Deposit(r) => r.inner,
1234 }
1235 }
1236}
1237
1238impl From<TypedReceipt<Receipt<alloy_rpc_types::Log>>> for OtsReceipt {
1239 fn from(value: TypedReceipt<Receipt<alloy_rpc_types::Log>>) -> Self {
1240 let r#type = match value {
1241 TypedReceipt::Legacy(_) => 0x00,
1242 TypedReceipt::EIP2930(_) => 0x01,
1243 TypedReceipt::EIP1559(_) => 0x02,
1244 TypedReceipt::EIP4844(_) => 0x03,
1245 TypedReceipt::EIP7702(_) => 0x04,
1246 TypedReceipt::Deposit(_) => 0x7E,
1247 } as u8;
1248 let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
1249 let status = receipt.status();
1250 let cumulative_gas_used = receipt.cumulative_gas_used();
1251 let logs = receipt.logs().to_vec();
1252 let logs_bloom = receipt.logs_bloom;
1253
1254 Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
1255 }
1256}
1257
1258impl TypedReceipt {
1259 pub fn cumulative_gas_used(&self) -> u64 {
1260 self.as_receipt_with_bloom().cumulative_gas_used()
1261 }
1262
1263 pub fn logs_bloom(&self) -> &Bloom {
1264 &self.as_receipt_with_bloom().logs_bloom
1265 }
1266
1267 pub fn logs(&self) -> &[Log] {
1268 self.as_receipt_with_bloom().logs()
1269 }
1270}
1271
1272impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceipt<Receipt<alloy_rpc_types::Log>> {
1273 fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
1274 match value {
1275 ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
1276 ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r),
1277 ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r),
1278 ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r),
1279 ReceiptEnvelope::Eip7702(r) => Self::EIP7702(r),
1280 }
1281 }
1282}
1283
1284impl Encodable for TypedReceipt {
1285 fn encode(&self, out: &mut dyn bytes::BufMut) {
1286 match self {
1287 Self::Legacy(r) => r.encode(out),
1288 receipt => {
1289 let payload_len = match receipt {
1290 Self::EIP2930(r) => r.length() + 1,
1291 Self::EIP1559(r) => r.length() + 1,
1292 Self::EIP4844(r) => r.length() + 1,
1293 Self::EIP7702(r) => r.length() + 1,
1294 Self::Deposit(r) => r.length() + 1,
1295 _ => unreachable!("receipt already matched"),
1296 };
1297
1298 match receipt {
1299 Self::EIP2930(r) => {
1300 Header { list: true, payload_length: payload_len }.encode(out);
1301 1u8.encode(out);
1302 r.encode(out);
1303 }
1304 Self::EIP1559(r) => {
1305 Header { list: true, payload_length: payload_len }.encode(out);
1306 2u8.encode(out);
1307 r.encode(out);
1308 }
1309 Self::EIP4844(r) => {
1310 Header { list: true, payload_length: payload_len }.encode(out);
1311 3u8.encode(out);
1312 r.encode(out);
1313 }
1314 Self::EIP7702(r) => {
1315 Header { list: true, payload_length: payload_len }.encode(out);
1316 4u8.encode(out);
1317 r.encode(out);
1318 }
1319 Self::Deposit(r) => {
1320 Header { list: true, payload_length: payload_len }.encode(out);
1321 0x7Eu8.encode(out);
1322 r.encode(out);
1323 }
1324 _ => unreachable!("receipt already matched"),
1325 }
1326 }
1327 }
1328 }
1329}
1330
1331impl Decodable for TypedReceipt {
1332 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1333 use bytes::Buf;
1334 use std::cmp::Ordering;
1335
1336 let rlp_type = *buf
1340 .first()
1341 .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?;
1342
1343 match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
1344 Ordering::Less => {
1345 let _header = Header::decode(buf)?;
1347 let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
1348 "typed receipt cannot be decoded from an empty slice",
1349 ))?;
1350 if receipt_type == 0x01 {
1351 buf.advance(1);
1352 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP2930)
1353 } else if receipt_type == 0x02 {
1354 buf.advance(1);
1355 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP1559)
1356 } else if receipt_type == 0x03 {
1357 buf.advance(1);
1358 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP4844)
1359 } else if receipt_type == 0x04 {
1360 buf.advance(1);
1361 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP7702)
1362 } else if receipt_type == 0x7E {
1363 buf.advance(1);
1364 <DepositReceipt as Decodable>::decode(buf).map(TypedReceipt::Deposit)
1365 } else {
1366 Err(alloy_rlp::Error::Custom("invalid receipt type"))
1367 }
1368 }
1369 Ordering::Equal => {
1370 Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding"))
1371 }
1372 Ordering::Greater => {
1373 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::Legacy)
1374 }
1375 }
1376 }
1377}
1378
1379impl Typed2718 for TypedReceipt {
1380 fn ty(&self) -> u8 {
1381 match self {
1382 Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID,
1383 Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID,
1384 Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID,
1385 Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID,
1386 Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID,
1387 Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
1388 }
1389 }
1390}
1391
1392impl Encodable2718 for TypedReceipt {
1393 fn encode_2718_len(&self) -> usize {
1394 match self {
1395 Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(),
1396 Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(),
1397 Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(),
1398 Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(),
1399 Self::EIP7702(r) => 1 + r.length(),
1400 Self::Deposit(r) => 1 + r.length(),
1401 }
1402 }
1403
1404 fn encode_2718(&self, out: &mut dyn BufMut) {
1405 if let Some(ty) = self.type_flag() {
1406 out.put_u8(ty);
1407 }
1408 match self {
1409 Self::Legacy(r)
1410 | Self::EIP2930(r)
1411 | Self::EIP1559(r)
1412 | Self::EIP4844(r)
1413 | Self::EIP7702(r) => r.encode(out),
1414 Self::Deposit(r) => r.encode(out),
1415 }
1416 }
1417}
1418
1419impl Decodable2718 for TypedReceipt {
1420 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1421 if ty == 0x7E {
1422 return Ok(Self::Deposit(DepositReceipt::decode(buf)?));
1423 }
1424 match ReceiptEnvelope::typed_decode(ty, buf)? {
1425 ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1426 ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1427 ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1428 ReceiptEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
1429 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
1430 }
1431 }
1432
1433 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1434 match ReceiptEnvelope::fallback_decode(buf)? {
1435 ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1436 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
1437 }
1438 }
1439}
1440
1441pub type ReceiptResponse = TransactionReceipt<TypedReceipt<Receipt<alloy_rpc_types::Log>>>;
1442
1443pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
1444 let WithOtherFields {
1445 inner:
1446 TransactionReceipt {
1447 transaction_hash,
1448 transaction_index,
1449 block_hash,
1450 block_number,
1451 gas_used,
1452 contract_address,
1453 effective_gas_price,
1454 from,
1455 to,
1456 blob_gas_price,
1457 blob_gas_used,
1458 inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
1459 },
1460 other,
1461 } = receipt;
1462
1463 Some(TransactionReceipt {
1464 transaction_hash,
1465 transaction_index,
1466 block_hash,
1467 block_number,
1468 gas_used,
1469 contract_address,
1470 effective_gas_price,
1471 from,
1472 to,
1473 blob_gas_price,
1474 blob_gas_used,
1475 inner: match r#type {
1476 0x00 => TypedReceipt::Legacy(receipt_with_bloom),
1477 0x01 => TypedReceipt::EIP2930(receipt_with_bloom),
1478 0x02 => TypedReceipt::EIP1559(receipt_with_bloom),
1479 0x03 => TypedReceipt::EIP4844(receipt_with_bloom),
1480 0x04 => TypedReceipt::EIP7702(receipt_with_bloom),
1481 0x7E => TypedReceipt::Deposit(DepositReceipt {
1482 inner: receipt_with_bloom,
1483 deposit_nonce: other
1484 .get_deserialized::<U64>("depositNonce")
1485 .transpose()
1486 .ok()?
1487 .map(|v| v.to()),
1488 deposit_receipt_version: other
1489 .get_deserialized::<U64>("depositReceiptVersion")
1490 .transpose()
1491 .ok()?
1492 .map(|v| v.to()),
1493 }),
1494 _ => return None,
1495 },
1496 })
1497}
1498
1499#[cfg(test)]
1500mod tests {
1501 use super::*;
1502 use alloy_primitives::{LogData, b256, hex};
1503 use std::str::FromStr;
1504
1505 #[test]
1507 fn test_receipt_convert() {
1508 let s = r#"{"type":"0x4","status":"0x1","cumulativeGasUsed":"0x903fd1","logs":[{"address":"0x0000d9fcd47bf761e7287d8ee09917d7e2100000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000234ce51365b9c417171b6dad280f49143e1b0547"],"data":"0x00000000000000000000000000000000000000000000032139b42c3431700000","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","blockTimestamp":"0x68411f7b","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","logIndex":"0x158","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000020000200000000000000800000000800000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","gasUsed":"0x28ee7","effectiveGasPrice":"0x4bf02090","from":"0x234ce51365b9c417171b6dad280f49143e1b0547","to":"0x234ce51365b9c417171b6dad280f49143e1b0547","contractAddress":null}"#;
1509 let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap();
1510 let _converted = convert_to_anvil_receipt(receipt).unwrap();
1511 }
1512
1513 #[test]
1514 fn test_decode_call() {
1515 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
1516 let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap();
1517
1518 let tx = TxLegacy {
1519 nonce: 2u64,
1520 gas_price: 1000000000u128,
1521 gas_limit: 100000,
1522 to: TxKind::Call(Address::from_slice(
1523 &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
1524 )),
1525 value: U256::from(1000000000000000u64),
1526 input: Bytes::default(),
1527 chain_id: Some(4),
1528 };
1529
1530 let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
1531
1532 let tx = TypedTransaction::Legacy(Signed::new_unchecked(
1533 tx,
1534 signature,
1535 b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
1536 ));
1537
1538 assert_eq!(tx, decoded);
1539 }
1540
1541 #[test]
1542 fn test_decode_create_goerli() {
1543 let tx_bytes =
1545 hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
1546 .unwrap();
1547 let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap();
1548 }
1549
1550 #[test]
1551 fn can_recover_sender() {
1552 let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
1554
1555 let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1556 panic!("decoding TypedTransaction failed");
1557 };
1558
1559 assert_eq!(
1560 tx.hash(),
1561 &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
1562 .parse::<B256>()
1563 .unwrap()
1564 );
1565 assert_eq!(
1566 tx.recover_signer().unwrap(),
1567 "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
1568 );
1569 }
1570
1571 #[test]
1574 fn test_decode_live_4844_tx() {
1575 use alloy_primitives::{address, b256};
1576
1577 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1579 let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1580 assert_eq!(res.r#type(), Some(3));
1581
1582 let tx = match res {
1583 TypedTransaction::EIP4844(tx) => tx,
1584 _ => unreachable!(),
1585 };
1586
1587 assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
1588
1589 assert_eq!(
1590 tx.tx().tx().blob_versioned_hashes,
1591 vec![
1592 b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1593 b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1594 b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1595 b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1596 b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1597 ]
1598 );
1599
1600 let from = tx.recover_signer().unwrap();
1601 assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1602 }
1603
1604 #[test]
1605 fn test_decode_encode_deposit_tx() {
1606 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
1608 .parse::<TxHash>()
1609 .unwrap();
1610
1611 let raw_tx = alloy_primitives::hex::decode(
1613 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
1614 )
1615 .unwrap();
1616 let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1617
1618 let mut encoded = Vec::new();
1619 dep_tx.encode_2718(&mut encoded);
1620
1621 assert_eq!(raw_tx, encoded);
1622
1623 assert_eq!(tx_hash, dep_tx.hash());
1624 }
1625
1626 #[test]
1627 fn can_recover_sender_not_normalized() {
1628 let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
1629
1630 let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1631 panic!("decoding TypedTransaction failed");
1632 };
1633
1634 assert_eq!(tx.tx().input, Bytes::from(b""));
1635 assert_eq!(tx.tx().gas_price, 1);
1636 assert_eq!(tx.tx().gas_limit, 21000);
1637 assert_eq!(tx.tx().nonce, 0);
1638 if let TxKind::Call(to) = tx.tx().to {
1639 assert_eq!(
1640 to,
1641 "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
1642 );
1643 } else {
1644 panic!("expected a call transaction");
1645 }
1646 assert_eq!(tx.tx().value, U256::from(0x0au64));
1647 assert_eq!(
1648 tx.recover_signer().unwrap(),
1649 "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
1650 );
1651 }
1652
1653 #[test]
1654 fn encode_legacy_receipt() {
1655 let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1656
1657 let mut data = vec![];
1658 let receipt = TypedReceipt::Legacy(ReceiptWithBloom {
1659 receipt: Receipt {
1660 status: false.into(),
1661 cumulative_gas_used: 0x1,
1662 logs: vec![Log {
1663 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1664 data: LogData::new_unchecked(
1665 vec![
1666 B256::from_str(
1667 "000000000000000000000000000000000000000000000000000000000000dead",
1668 )
1669 .unwrap(),
1670 B256::from_str(
1671 "000000000000000000000000000000000000000000000000000000000000beef",
1672 )
1673 .unwrap(),
1674 ],
1675 Bytes::from_str("0100ff").unwrap(),
1676 ),
1677 }],
1678 },
1679 logs_bloom: [0; 256].into(),
1680 });
1681
1682 receipt.encode(&mut data);
1683
1684 assert_eq!(receipt.length(), expected.len());
1686 assert_eq!(data, expected);
1687 }
1688
1689 #[test]
1690 fn decode_legacy_receipt() {
1691 let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1692
1693 let expected = TypedReceipt::Legacy(ReceiptWithBloom {
1694 receipt: Receipt {
1695 status: false.into(),
1696 cumulative_gas_used: 0x1,
1697 logs: vec![Log {
1698 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1699 data: LogData::new_unchecked(
1700 vec![
1701 B256::from_str(
1702 "000000000000000000000000000000000000000000000000000000000000dead",
1703 )
1704 .unwrap(),
1705 B256::from_str(
1706 "000000000000000000000000000000000000000000000000000000000000beef",
1707 )
1708 .unwrap(),
1709 ],
1710 Bytes::from_str("0100ff").unwrap(),
1711 ),
1712 }],
1713 },
1714 logs_bloom: [0; 256].into(),
1715 });
1716
1717 let receipt = TypedReceipt::decode(&mut &data[..]).unwrap();
1718
1719 assert_eq!(receipt, expected);
1720 }
1721
1722 #[test]
1723 fn deser_to_type_tx() {
1724 let tx = r#"
1725 {
1726 "EIP1559": {
1727 "chainId": "0x7a69",
1728 "nonce": "0x0",
1729 "gas": "0x5209",
1730 "maxFeePerGas": "0x77359401",
1731 "maxPriorityFeePerGas": "0x1",
1732 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1733 "value": "0x0",
1734 "accessList": [],
1735 "input": "0x",
1736 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
1737 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
1738 "yParity": "0x0",
1739 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
1740 }
1741 }"#;
1742
1743 let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap();
1744 }
1745}