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