1use alloy_consensus::{
3 Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930,
4 TxEnvelope, TxLegacy, TxReceipt, Typed2718,
5 constants::{
6 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
7 LEGACY_TX_TYPE_ID,
8 },
9 transaction::{
10 Recovered, TxEip7702,
11 eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
12 },
13};
14
15use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
16use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope};
17use alloy_primitives::{Address, B256, Bloom, Bytes, Signature, TxHash, TxKind, U64, U256};
18use alloy_rlp::{Decodable, Encodable, Header};
19use alloy_rpc_types::{
20 AccessList, ConversionError, Transaction as RpcTransaction, TransactionReceipt,
21 request::TransactionRequest, trace::otterscan::OtsReceipt,
22};
23use alloy_serde::{OtherFields, WithOtherFields};
24use bytes::BufMut;
25use foundry_evm::traces::CallTraceNode;
26
27use op_alloy_consensus::{
28 DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom, TxDeposit,
29};
30use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
31use revm::{context::TxEnv, interpreter::InstructionResult};
32use serde::{Deserialize, Serialize};
33use std::ops::{Deref, Mul};
34
35pub fn transaction_request_to_typed(
38 tx: WithOtherFields<TransactionRequest>,
39) -> Option<TypedTransactionRequest> {
40 let WithOtherFields::<TransactionRequest> {
41 inner:
42 TransactionRequest {
43 from,
44 to,
45 gas_price,
46 max_fee_per_gas,
47 max_priority_fee_per_gas,
48 max_fee_per_blob_gas,
49 blob_versioned_hashes,
50 gas,
51 value,
52 input,
53 nonce,
54 access_list,
55 sidecar,
56 transaction_type,
57 authorization_list,
58 chain_id: _,
59 },
60 other,
61 } = tx;
62
63 if transaction_type == Some(0x7E) || has_optimism_fields(&other) {
65 let mint = other.get_deserialized::<U256>("mint")?.map(|m| m.to::<u128>()).ok()?;
66
67 return Some(TypedTransactionRequest::Deposit(TxDeposit {
68 from: from.unwrap_or_default(),
69 source_hash: other.get_deserialized::<B256>("sourceHash")?.ok()?,
70 to: to.unwrap_or_default(),
71 mint,
72 value: value.unwrap_or_default(),
73 gas_limit: gas.unwrap_or_default(),
74 is_system_transaction: other.get_deserialized::<bool>("isSystemTx")?.ok()?,
75 input: input.into_input().unwrap_or_default(),
76 }));
77 }
78
79 if transaction_type == Some(4) || authorization_list.is_some() {
81 return Some(TypedTransactionRequest::EIP7702(TxEip7702 {
82 nonce: nonce.unwrap_or_default(),
83 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
84 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
85 gas_limit: gas.unwrap_or_default(),
86 value: value.unwrap_or(U256::ZERO),
87 input: input.into_input().unwrap_or_default(),
88 to: to?.into_to()?,
90 chain_id: 0,
91 access_list: access_list.unwrap_or_default(),
92 authorization_list: authorization_list.unwrap_or_default(),
93 }));
94 }
95
96 match (
97 transaction_type,
98 gas_price,
99 max_fee_per_gas,
100 max_priority_fee_per_gas,
101 access_list.as_ref(),
102 max_fee_per_blob_gas,
103 blob_versioned_hashes.as_ref(),
104 sidecar.as_ref(),
105 to,
106 ) {
107 (Some(0), _, None, None, None, None, None, None, _)
109 | (None, Some(_), None, None, None, None, None, None, _) => {
110 Some(TypedTransactionRequest::Legacy(TxLegacy {
111 nonce: nonce.unwrap_or_default(),
112 gas_price: gas_price.unwrap_or_default(),
113 gas_limit: gas.unwrap_or_default(),
114 value: value.unwrap_or(U256::ZERO),
115 input: input.into_input().unwrap_or_default(),
116 to: to.unwrap_or_default(),
117 chain_id: None,
118 }))
119 }
120 (Some(1), _, None, None, _, None, None, None, _)
122 | (None, _, None, None, Some(_), None, None, None, _) => {
123 Some(TypedTransactionRequest::EIP2930(TxEip2930 {
124 nonce: nonce.unwrap_or_default(),
125 gas_price: gas_price.unwrap_or_default(),
126 gas_limit: gas.unwrap_or_default(),
127 value: value.unwrap_or(U256::ZERO),
128 input: input.into_input().unwrap_or_default(),
129 to: to.unwrap_or_default(),
130 chain_id: 0,
131 access_list: access_list.unwrap_or_default(),
132 }))
133 }
134 (Some(2), None, _, _, _, _, None, None, _)
136 | (None, None, Some(_), _, _, _, None, None, _)
137 | (None, None, _, Some(_), _, _, None, None, _)
138 | (None, None, None, None, None, _, None, None, _) => {
139 Some(TypedTransactionRequest::EIP1559(TxEip1559 {
141 nonce: nonce.unwrap_or_default(),
142 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
143 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
144 gas_limit: gas.unwrap_or_default(),
145 value: value.unwrap_or(U256::ZERO),
146 input: input.into_input().unwrap_or_default(),
147 to: to.unwrap_or_default(),
148 chain_id: 0,
149 access_list: access_list.unwrap_or_default(),
150 }))
151 }
152 (Some(3), None, _, _, _, _, Some(_), _, to) => {
154 let tx = TxEip4844 {
155 nonce: nonce.unwrap_or_default(),
156 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
157 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
158 max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(),
159 gas_limit: gas.unwrap_or_default(),
160 value: value.unwrap_or(U256::ZERO),
161 input: input.into_input().unwrap_or_default(),
162 to: match to.unwrap_or(TxKind::Create) {
163 TxKind::Call(to) => to,
164 TxKind::Create => Address::ZERO,
165 },
166 chain_id: 0,
167 access_list: access_list.unwrap_or_default(),
168 blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(),
169 };
170
171 if let Some(sidecar) = sidecar {
172 Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar(
173 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar),
174 )))
175 } else {
176 Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx)))
177 }
178 }
179 _ => None,
180 }
181}
182
183pub fn has_optimism_fields(other: &OtherFields) -> bool {
184 other.contains_key("sourceHash")
185 && other.contains_key("mint")
186 && other.contains_key("isSystemTx")
187}
188
189#[derive(Clone, Debug, PartialEq, Eq)]
190pub enum TypedTransactionRequest {
191 Legacy(TxLegacy),
192 EIP2930(TxEip2930),
193 EIP1559(TxEip1559),
194 EIP7702(TxEip7702),
195 EIP4844(TxEip4844Variant),
196 Deposit(TxDeposit),
197}
198
199#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
204pub struct MaybeImpersonatedTransaction {
205 pub transaction: TypedTransaction,
206 pub impersonated_sender: Option<Address>,
207}
208
209impl Typed2718 for MaybeImpersonatedTransaction {
210 fn ty(&self) -> u8 {
211 self.transaction.ty()
212 }
213}
214
215impl MaybeImpersonatedTransaction {
216 pub fn new(transaction: TypedTransaction) -> Self {
218 Self { transaction, impersonated_sender: None }
219 }
220
221 pub fn impersonated(transaction: TypedTransaction, impersonated_sender: Address) -> Self {
223 Self { transaction, impersonated_sender: Some(impersonated_sender) }
224 }
225
226 pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
228 if let Some(sender) = self.impersonated_sender {
229 return Ok(sender);
230 }
231 self.transaction.recover()
232 }
233
234 pub fn is_impersonated(&self) -> bool {
236 self.impersonated_sender.is_some()
237 }
238
239 pub fn hash(&self) -> B256 {
241 if let Some(sender) = self.impersonated_sender {
242 return self.transaction.impersonated_hash(sender);
243 }
244 self.transaction.hash()
245 }
246
247 pub fn into_rpc_transaction(self) -> RpcTransaction {
249 let hash = self.hash();
250 let from = self.recover().unwrap_or_default();
251 let envelope = self.transaction.try_into_eth().expect("cant build deposit transactions");
252
253 let inner_envelope = match envelope {
256 TxEnvelope::Legacy(t) => {
257 let (tx, sig, _) = t.into_parts();
258 TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash))
259 }
260 TxEnvelope::Eip2930(t) => {
261 let (tx, sig, _) = t.into_parts();
262 TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash))
263 }
264 TxEnvelope::Eip1559(t) => {
265 let (tx, sig, _) = t.into_parts();
266 TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash))
267 }
268 TxEnvelope::Eip4844(t) => {
269 let (tx, sig, _) = t.into_parts();
270 TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash))
271 }
272 TxEnvelope::Eip7702(t) => {
273 let (tx, sig, _) = t.into_parts();
274 TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash))
275 }
276 };
277
278 RpcTransaction {
279 block_hash: None,
280 block_number: None,
281 transaction_index: None,
282 effective_gas_price: None,
283 inner: Recovered::new_unchecked(inner_envelope, from),
284 }
285 }
286}
287
288impl Encodable2718 for MaybeImpersonatedTransaction {
289 fn encode_2718_len(&self) -> usize {
290 self.transaction.encode_2718_len()
291 }
292
293 fn encode_2718(&self, out: &mut dyn BufMut) {
294 self.transaction.encode_2718(out)
295 }
296}
297
298impl Encodable for MaybeImpersonatedTransaction {
299 fn encode(&self, out: &mut dyn bytes::BufMut) {
300 self.transaction.encode(out)
301 }
302}
303
304impl From<MaybeImpersonatedTransaction> for TypedTransaction {
305 fn from(value: MaybeImpersonatedTransaction) -> Self {
306 value.transaction
307 }
308}
309
310impl From<TypedTransaction> for MaybeImpersonatedTransaction {
311 fn from(value: TypedTransaction) -> Self {
312 Self::new(value)
313 }
314}
315
316impl Decodable for MaybeImpersonatedTransaction {
317 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
318 TypedTransaction::decode(buf).map(Self::new)
319 }
320}
321
322impl AsRef<TypedTransaction> for MaybeImpersonatedTransaction {
323 fn as_ref(&self) -> &TypedTransaction {
324 &self.transaction
325 }
326}
327
328impl Deref for MaybeImpersonatedTransaction {
329 type Target = TypedTransaction;
330
331 fn deref(&self) -> &Self::Target {
332 &self.transaction
333 }
334}
335
336impl From<MaybeImpersonatedTransaction> for RpcTransaction {
337 fn from(value: MaybeImpersonatedTransaction) -> Self {
338 value.into_rpc_transaction()
339 }
340}
341
342#[derive(Clone, Debug, PartialEq, Eq)]
344pub struct PendingTransaction {
345 pub transaction: MaybeImpersonatedTransaction,
347 sender: Address,
349 hash: TxHash,
351}
352
353impl PendingTransaction {
354 pub fn new(transaction: TypedTransaction) -> Result<Self, alloy_primitives::SignatureError> {
355 let sender = transaction.recover()?;
356 let hash = transaction.hash();
357 Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
358 }
359
360 pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self {
361 let hash = transaction.impersonated_hash(sender);
362 Self {
363 transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
364 sender,
365 hash,
366 }
367 }
368
369 pub fn from_maybe_impersonated(
371 transaction: MaybeImpersonatedTransaction,
372 ) -> Result<Self, alloy_primitives::SignatureError> {
373 if let Some(impersonated) = transaction.impersonated_sender {
374 Ok(Self::with_impersonated(transaction.transaction, impersonated))
375 } else {
376 Self::new(transaction.transaction)
377 }
378 }
379
380 pub fn nonce(&self) -> u64 {
381 self.transaction.nonce()
382 }
383
384 pub fn hash(&self) -> &TxHash {
385 &self.hash
386 }
387
388 pub fn sender(&self) -> &Address {
389 &self.sender
390 }
391
392 pub fn to_revm_tx_env(&self) -> OpTransaction<TxEnv> {
397 fn transact_to(kind: &TxKind) -> TxKind {
398 match kind {
399 TxKind::Call(c) => TxKind::Call(*c),
400 TxKind::Create => TxKind::Create,
401 }
402 }
403
404 let caller = *self.sender();
405 match &self.transaction.transaction {
406 TypedTransaction::Legacy(tx) => {
407 let chain_id = tx.tx().chain_id;
408 let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx();
409 OpTransaction::new(TxEnv {
410 caller,
411 kind: transact_to(to),
412 data: input.clone(),
413 chain_id,
414 nonce: *nonce,
415 value: (*value),
416 gas_price: *gas_price,
417 gas_priority_fee: None,
418 gas_limit: *gas_limit,
419 access_list: vec![].into(),
420 tx_type: LEGACY_TX_TYPE_ID,
421 ..Default::default()
422 })
423 }
424 TypedTransaction::EIP2930(tx) => {
425 let TxEip2930 {
426 chain_id,
427 nonce,
428 gas_price,
429 gas_limit,
430 to,
431 value,
432 input,
433 access_list,
434 ..
435 } = tx.tx();
436 OpTransaction::new(TxEnv {
437 caller,
438 kind: transact_to(to),
439 data: input.clone(),
440 chain_id: Some(*chain_id),
441 nonce: *nonce,
442 value: *value,
443 gas_price: *gas_price,
444 gas_priority_fee: None,
445 gas_limit: *gas_limit,
446 access_list: access_list.clone(),
447 tx_type: EIP2930_TX_TYPE_ID,
448 ..Default::default()
449 })
450 }
451 TypedTransaction::EIP1559(tx) => {
452 let TxEip1559 {
453 chain_id,
454 nonce,
455 max_priority_fee_per_gas,
456 max_fee_per_gas,
457 gas_limit,
458 to,
459 value,
460 input,
461 access_list,
462 ..
463 } = tx.tx();
464 OpTransaction::new(TxEnv {
465 caller,
466 kind: transact_to(to),
467 data: input.clone(),
468 chain_id: Some(*chain_id),
469 nonce: *nonce,
470 value: *value,
471 gas_price: *max_fee_per_gas,
472 gas_priority_fee: Some(*max_priority_fee_per_gas),
473 gas_limit: *gas_limit,
474 access_list: access_list.clone(),
475 tx_type: EIP1559_TX_TYPE_ID,
476 ..Default::default()
477 })
478 }
479 TypedTransaction::EIP4844(tx) => {
480 let TxEip4844 {
481 chain_id,
482 nonce,
483 max_fee_per_blob_gas,
484 max_fee_per_gas,
485 max_priority_fee_per_gas,
486 gas_limit,
487 to,
488 value,
489 input,
490 access_list,
491 blob_versioned_hashes,
492 ..
493 } = tx.tx().tx();
494 OpTransaction::new(TxEnv {
495 caller,
496 kind: TxKind::Call(*to),
497 data: input.clone(),
498 chain_id: Some(*chain_id),
499 nonce: *nonce,
500 value: *value,
501 gas_price: *max_fee_per_gas,
502 gas_priority_fee: Some(*max_priority_fee_per_gas),
503 max_fee_per_blob_gas: *max_fee_per_blob_gas,
504 blob_hashes: blob_versioned_hashes.clone(),
505 gas_limit: *gas_limit,
506 access_list: access_list.clone(),
507 tx_type: EIP4844_TX_TYPE_ID,
508 ..Default::default()
509 })
510 }
511 TypedTransaction::EIP7702(tx) => {
512 let TxEip7702 {
513 chain_id,
514 nonce,
515 gas_limit,
516 max_fee_per_gas,
517 max_priority_fee_per_gas,
518 to,
519 value,
520 access_list,
521 authorization_list,
522 input,
523 } = tx.tx();
524
525 let mut tx = TxEnv {
526 caller,
527 kind: TxKind::Call(*to),
528 data: input.clone(),
529 chain_id: Some(*chain_id),
530 nonce: *nonce,
531 value: *value,
532 gas_price: *max_fee_per_gas,
533 gas_priority_fee: Some(*max_priority_fee_per_gas),
534 gas_limit: *gas_limit,
535 access_list: access_list.clone(),
536 tx_type: EIP7702_TX_TYPE_ID,
537 ..Default::default()
538 };
539 tx.set_signed_authorization(authorization_list.clone());
540
541 OpTransaction::new(tx)
542 }
543 TypedTransaction::Deposit(tx) => {
544 let chain_id = tx.chain_id();
545 let TxDeposit {
546 source_hash,
547 to,
548 mint,
549 value,
550 gas_limit,
551 is_system_transaction,
552 input,
553 ..
554 } = tx;
555
556 let base = TxEnv {
557 caller,
558 kind: transact_to(to),
559 data: input.clone(),
560 chain_id,
561 nonce: 0,
562 value: *value,
563 gas_price: 0,
564 gas_priority_fee: None,
565 gas_limit: { *gas_limit },
566 access_list: vec![].into(),
567 tx_type: DEPOSIT_TX_TYPE_ID,
568 ..Default::default()
569 };
570
571 let deposit = DepositTransactionParts {
572 source_hash: *source_hash,
573 mint: Some(*mint),
574 is_system_transaction: *is_system_transaction,
575 };
576
577 OpTransaction { base, deposit, enveloped_tx: None }
578 }
579 }
580 }
581}
582
583#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
585pub enum TypedTransaction {
586 Legacy(Signed<TxLegacy>),
588 EIP2930(Signed<TxEip2930>),
590 EIP1559(Signed<TxEip1559>),
592 EIP4844(Signed<TxEip4844Variant>),
594 EIP7702(Signed<TxEip7702>),
596 Deposit(TxDeposit),
598}
599
600impl TryFrom<AnyRpcTransaction> for TypedTransaction {
601 type Error = ConversionError;
602
603 fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
604 let WithOtherFields { inner, .. } = value.0;
605 let from = inner.inner.signer();
606 match inner.inner.into_inner() {
607 AnyTxEnvelope::Ethereum(tx) => match tx {
608 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
609 TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
610 TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
611 TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
612 TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
613 },
614 AnyTxEnvelope::Unknown(mut tx) => {
615 if tx.ty() == DEPOSIT_TX_TYPE_ID {
617 tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap());
618 let deposit_tx =
619 tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
620 ConversionError::Custom(format!(
621 "Failed to deserialize deposit tx: {e}"
622 ))
623 })?;
624
625 return Ok(Self::Deposit(deposit_tx));
626 };
627
628 Err(ConversionError::Custom("UnknownTxType".to_string()))
629 }
630 }
631 }
632}
633
634impl TypedTransaction {
635 pub fn try_into_eth(self) -> Result<TxEnvelope, Self> {
640 match self {
641 Self::Legacy(tx) => Ok(TxEnvelope::Legacy(tx)),
642 Self::EIP2930(tx) => Ok(TxEnvelope::Eip2930(tx)),
643 Self::EIP1559(tx) => Ok(TxEnvelope::Eip1559(tx)),
644 Self::EIP4844(tx) => Ok(TxEnvelope::Eip4844(tx)),
645 Self::EIP7702(tx) => Ok(TxEnvelope::Eip7702(tx)),
646 Self::Deposit(_) => Err(self),
647 }
648 }
649
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(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1098#[serde(tag = "type")]
1099pub enum TypedReceipt {
1100 #[serde(rename = "0x0", alias = "0x00")]
1101 Legacy(ReceiptWithBloom<Receipt<alloy_primitives::Log>>),
1102 #[serde(rename = "0x1", alias = "0x01")]
1103 EIP2930(ReceiptWithBloom<Receipt<alloy_primitives::Log>>),
1104 #[serde(rename = "0x2", alias = "0x02")]
1105 EIP1559(ReceiptWithBloom<Receipt<alloy_primitives::Log>>),
1106 #[serde(rename = "0x3", alias = "0x03")]
1107 EIP4844(ReceiptWithBloom<Receipt<alloy_primitives::Log>>),
1108 #[serde(rename = "0x4", alias = "0x04")]
1109 EIP7702(ReceiptWithBloom<Receipt<alloy_primitives::Log>>),
1110 #[serde(rename = "0x7E", alias = "0x7e")]
1111 Deposit(OpDepositReceiptWithBloom),
1112}
1113
1114#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1116#[serde(tag = "type")]
1117pub enum TypedReceiptRpc {
1118 #[serde(rename = "0x0", alias = "0x00")]
1119 Legacy(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
1120 #[serde(rename = "0x1", alias = "0x01")]
1121 EIP2930(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
1122 #[serde(rename = "0x2", alias = "0x02")]
1123 EIP1559(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
1124 #[serde(rename = "0x3", alias = "0x03")]
1125 EIP4844(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
1126 #[serde(rename = "0x4", alias = "0x04")]
1127 EIP7702(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
1128 #[serde(rename = "0x7E", alias = "0x7e")]
1129 Deposit(OpDepositReceiptWithBloom),
1130}
1131
1132impl TypedReceipt {
1133 pub fn into_rpc_receipt(self) -> TypedReceiptRpc {
1135 match self {
1136 Self::Legacy(r) => TypedReceiptRpc::Legacy(convert_receipt_to_rpc(r)),
1137 Self::EIP2930(r) => TypedReceiptRpc::EIP2930(convert_receipt_to_rpc(r)),
1138 Self::EIP1559(r) => TypedReceiptRpc::EIP1559(convert_receipt_to_rpc(r)),
1139 Self::EIP4844(r) => TypedReceiptRpc::EIP4844(convert_receipt_to_rpc(r)),
1140 Self::EIP7702(r) => TypedReceiptRpc::EIP7702(convert_receipt_to_rpc(r)),
1141 Self::Deposit(r) => TypedReceiptRpc::Deposit(r),
1142 }
1143 }
1144
1145 pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<Receipt<alloy_primitives::Log>> {
1146 match self {
1147 Self::Legacy(r)
1148 | Self::EIP1559(r)
1149 | Self::EIP2930(r)
1150 | Self::EIP4844(r)
1151 | Self::EIP7702(r) => r,
1152 Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
1153 }
1154 }
1155
1156 pub fn logs(&self) -> &[alloy_primitives::Log] {
1157 match self {
1158 Self::Legacy(r)
1159 | Self::EIP1559(r)
1160 | Self::EIP2930(r)
1161 | Self::EIP4844(r)
1162 | Self::EIP7702(r) => &r.receipt.logs,
1163 Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
1164 }
1165 }
1166
1167 pub fn logs_bloom(&self) -> &Bloom {
1168 match self {
1169 Self::Legacy(r)
1170 | Self::EIP1559(r)
1171 | Self::EIP2930(r)
1172 | Self::EIP4844(r)
1173 | Self::EIP7702(r) => &r.logs_bloom,
1174 Self::Deposit(r) => &r.logs_bloom,
1175 }
1176 }
1177
1178 pub fn cumulative_gas_used(&self) -> u64 {
1179 match self {
1180 Self::Deposit(r) => r.receipt.inner.cumulative_gas_used,
1181 _ => self.as_receipt_with_bloom().cumulative_gas_used(),
1182 }
1183 }
1184}
1185
1186fn convert_receipt_to_rpc(
1188 receipt: ReceiptWithBloom<Receipt<alloy_primitives::Log>>,
1189) -> ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
1190 receipt.map_logs(|log| alloy_rpc_types::Log {
1191 inner: log,
1192 block_hash: None,
1193 block_number: None,
1194 block_timestamp: None,
1195 transaction_hash: None,
1196 transaction_index: None,
1197 log_index: None,
1198 removed: false,
1199 })
1200}
1201
1202impl TypedReceiptRpc {
1203 pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
1204 match self {
1205 Self::Legacy(r)
1206 | Self::EIP1559(r)
1207 | Self::EIP2930(r)
1208 | Self::EIP4844(r)
1209 | Self::EIP7702(r) => r,
1210 Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
1211 }
1212 }
1213
1214 pub fn logs_bloom(&self) -> &Bloom {
1215 match self {
1216 Self::Legacy(r)
1217 | Self::EIP1559(r)
1218 | Self::EIP2930(r)
1219 | Self::EIP4844(r)
1220 | Self::EIP7702(r) => &r.logs_bloom,
1221 Self::Deposit(r) => &r.logs_bloom,
1222 }
1223 }
1224
1225 pub fn logs(&self) -> &[alloy_rpc_types::Log] {
1226 match self {
1227 Self::Legacy(r)
1228 | Self::EIP1559(r)
1229 | Self::EIP2930(r)
1230 | Self::EIP4844(r)
1231 | Self::EIP7702(r) => &r.receipt.logs,
1232 Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
1233 }
1234 }
1235
1236 pub fn cumulative_gas_used(&self) -> u64 {
1237 self.as_receipt_with_bloom().cumulative_gas_used()
1238 }
1239}
1240
1241impl From<TypedReceiptRpc> for ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
1243 fn from(value: TypedReceiptRpc) -> Self {
1244 match value {
1245 TypedReceiptRpc::Legacy(r)
1246 | TypedReceiptRpc::EIP1559(r)
1247 | TypedReceiptRpc::EIP2930(r)
1248 | TypedReceiptRpc::EIP4844(r)
1249 | TypedReceiptRpc::EIP7702(r) => r,
1250 TypedReceiptRpc::Deposit(r) => {
1251 let receipt = Receipt::<alloy_rpc_types::Log> {
1253 status: r.receipt.inner.status,
1254 cumulative_gas_used: r.receipt.inner.cumulative_gas_used,
1255 logs: r
1256 .receipt
1257 .inner
1258 .logs
1259 .into_iter()
1260 .map(|l| alloy_rpc_types::Log {
1261 inner: l,
1262 block_hash: None,
1263 block_number: None,
1264 block_timestamp: None,
1265 transaction_hash: None,
1266 transaction_index: None,
1267 log_index: None,
1268 removed: false,
1269 })
1270 .collect(),
1271 };
1272 Self { receipt, logs_bloom: r.logs_bloom }
1273 }
1274 }
1275 }
1276}
1277
1278impl From<TypedReceiptRpc> for OtsReceipt {
1279 fn from(value: TypedReceiptRpc) -> Self {
1280 let r#type = match value {
1281 TypedReceiptRpc::Legacy(_) => 0x00,
1282 TypedReceiptRpc::EIP2930(_) => 0x01,
1283 TypedReceiptRpc::EIP1559(_) => 0x02,
1284 TypedReceiptRpc::EIP4844(_) => 0x03,
1285 TypedReceiptRpc::EIP7702(_) => 0x04,
1286 TypedReceiptRpc::Deposit(_) => 0x7E,
1287 } as u8;
1288 let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
1289 let status = receipt.status();
1290 let cumulative_gas_used = receipt.cumulative_gas_used();
1291 let logs = receipt.logs().to_vec();
1292 let logs_bloom = receipt.logs_bloom;
1293
1294 Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
1295 }
1296}
1297
1298impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceiptRpc {
1299 fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
1300 match value {
1301 ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
1302 ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r),
1303 ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r),
1304 ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r),
1305 ReceiptEnvelope::Eip7702(r) => Self::EIP7702(r),
1306 }
1307 }
1308}
1309
1310impl Encodable for TypedReceipt {
1311 fn encode(&self, out: &mut dyn bytes::BufMut) {
1312 match self {
1313 Self::Legacy(r) => r.encode(out),
1314 receipt => {
1315 let payload_len = match receipt {
1316 Self::EIP2930(r) => r.length() + 1,
1317 Self::EIP1559(r) => r.length() + 1,
1318 Self::EIP4844(r) => r.length() + 1,
1319 Self::EIP7702(r) => r.length() + 1,
1320 Self::Deposit(r) => r.length() + 1,
1321 _ => unreachable!("receipt already matched"),
1322 };
1323
1324 match receipt {
1325 Self::EIP2930(r) => {
1326 Header { list: true, payload_length: payload_len }.encode(out);
1327 1u8.encode(out);
1328 r.encode(out);
1329 }
1330 Self::EIP1559(r) => {
1331 Header { list: true, payload_length: payload_len }.encode(out);
1332 2u8.encode(out);
1333 r.encode(out);
1334 }
1335 Self::EIP4844(r) => {
1336 Header { list: true, payload_length: payload_len }.encode(out);
1337 3u8.encode(out);
1338 r.encode(out);
1339 }
1340 Self::EIP7702(r) => {
1341 Header { list: true, payload_length: payload_len }.encode(out);
1342 4u8.encode(out);
1343 r.encode(out);
1344 }
1345 Self::Deposit(r) => {
1346 Header { list: true, payload_length: payload_len }.encode(out);
1347 0x7Eu8.encode(out);
1348 r.encode(out);
1349 }
1350 _ => unreachable!("receipt already matched"),
1351 }
1352 }
1353 }
1354 }
1355}
1356
1357impl Decodable for TypedReceipt {
1358 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1359 use bytes::Buf;
1360 use std::cmp::Ordering;
1361
1362 let rlp_type = *buf
1366 .first()
1367 .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?;
1368
1369 match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
1370 Ordering::Less => {
1371 let _header = Header::decode(buf)?;
1373 let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
1374 "typed receipt cannot be decoded from an empty slice",
1375 ))?;
1376 if receipt_type == 0x01 {
1377 buf.advance(1);
1378 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP2930)
1379 } else if receipt_type == 0x02 {
1380 buf.advance(1);
1381 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP1559)
1382 } else if receipt_type == 0x03 {
1383 buf.advance(1);
1384 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP4844)
1385 } else if receipt_type == 0x04 {
1386 buf.advance(1);
1387 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP7702)
1388 } else if receipt_type == 0x7E {
1389 buf.advance(1);
1390 <OpDepositReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::Deposit)
1391 } else {
1392 Err(alloy_rlp::Error::Custom("invalid receipt type"))
1393 }
1394 }
1395 Ordering::Equal => {
1396 Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding"))
1397 }
1398 Ordering::Greater => {
1399 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::Legacy)
1400 }
1401 }
1402 }
1403}
1404
1405impl Typed2718 for TypedReceipt {
1406 fn ty(&self) -> u8 {
1407 match self {
1408 Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID,
1409 Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID,
1410 Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID,
1411 Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID,
1412 Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID,
1413 Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
1414 }
1415 }
1416}
1417
1418impl Encodable2718 for TypedReceipt {
1419 fn encode_2718_len(&self) -> usize {
1420 match self {
1421 Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(),
1422 Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(),
1423 Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(),
1424 Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(),
1425 Self::EIP7702(r) => 1 + r.length(),
1426 Self::Deposit(r) => 1 + r.length(),
1427 }
1428 }
1429
1430 fn encode_2718(&self, out: &mut dyn BufMut) {
1431 if let Some(ty) = self.type_flag() {
1432 out.put_u8(ty);
1433 }
1434 match self {
1435 Self::Legacy(r)
1436 | Self::EIP2930(r)
1437 | Self::EIP1559(r)
1438 | Self::EIP4844(r)
1439 | Self::EIP7702(r) => r.encode(out),
1440 Self::Deposit(r) => r.encode(out),
1441 }
1442 }
1443}
1444
1445impl Decodable2718 for TypedReceipt {
1446 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1447 if ty == 0x7E {
1448 return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?));
1449 }
1450 match ReceiptEnvelope::typed_decode(ty, buf)? {
1451 ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1452 ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1453 ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1454 ReceiptEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
1455 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
1456 }
1457 }
1458
1459 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1460 match ReceiptEnvelope::fallback_decode(buf)? {
1461 ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1462 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
1463 }
1464 }
1465}
1466
1467pub type ReceiptResponse = WithOtherFields<TransactionReceipt<TypedReceiptRpc>>;
1468
1469pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
1470 let WithOtherFields {
1471 inner:
1472 TransactionReceipt {
1473 transaction_hash,
1474 transaction_index,
1475 block_hash,
1476 block_number,
1477 gas_used,
1478 contract_address,
1479 effective_gas_price,
1480 from,
1481 to,
1482 blob_gas_price,
1483 blob_gas_used,
1484 inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
1485 },
1486 other,
1487 } = receipt;
1488
1489 Some(WithOtherFields {
1490 inner: 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: match r#type {
1503 0x00 => TypedReceiptRpc::Legacy(receipt_with_bloom),
1504 0x01 => TypedReceiptRpc::EIP2930(receipt_with_bloom),
1505 0x02 => TypedReceiptRpc::EIP1559(receipt_with_bloom),
1506 0x03 => TypedReceiptRpc::EIP4844(receipt_with_bloom),
1507 0x04 => TypedReceiptRpc::EIP7702(receipt_with_bloom),
1508 0x7E => TypedReceiptRpc::Deposit(OpDepositReceiptWithBloom {
1509 receipt: OpDepositReceipt {
1510 inner: Receipt {
1511 status: alloy_consensus::Eip658Value::Eip658(
1512 receipt_with_bloom.status(),
1513 ),
1514 cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(),
1515 logs: receipt_with_bloom
1516 .receipt
1517 .logs
1518 .into_iter()
1519 .map(|l| l.inner)
1520 .collect(),
1521 },
1522 deposit_nonce: other
1523 .get_deserialized::<U64>("depositNonce")
1524 .transpose()
1525 .ok()?
1526 .map(|v| v.to()),
1527 deposit_receipt_version: other
1528 .get_deserialized::<U64>("depositReceiptVersion")
1529 .transpose()
1530 .ok()?
1531 .map(|v| v.to()),
1532 },
1533 logs_bloom: receipt_with_bloom.logs_bloom,
1534 }),
1535 _ => return None,
1536 },
1537 },
1538 other,
1539 })
1540}
1541
1542#[cfg(test)]
1543mod tests {
1544 use super::*;
1545 use alloy_primitives::{Log, LogData, b256, hex};
1546 use std::str::FromStr;
1547
1548 #[test]
1550 fn test_receipt_convert() {
1551 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}"#;
1552 let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap();
1553 let _converted = convert_to_anvil_receipt(receipt).unwrap();
1554 }
1555
1556 #[test]
1557 fn test_decode_call() {
1558 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
1559 let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap();
1560
1561 let tx = TxLegacy {
1562 nonce: 2u64,
1563 gas_price: 1000000000u128,
1564 gas_limit: 100000,
1565 to: TxKind::Call(Address::from_slice(
1566 &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
1567 )),
1568 value: U256::from(1000000000000000u64),
1569 input: Bytes::default(),
1570 chain_id: Some(4),
1571 };
1572
1573 let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
1574
1575 let tx = TypedTransaction::Legacy(Signed::new_unchecked(
1576 tx,
1577 signature,
1578 b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
1579 ));
1580
1581 assert_eq!(tx, decoded);
1582 }
1583
1584 #[test]
1585 fn test_decode_create_goerli() {
1586 let tx_bytes =
1588 hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
1589 .unwrap();
1590 let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap();
1591 }
1592
1593 #[test]
1594 fn can_recover_sender() {
1595 let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
1597
1598 let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1599 panic!("decoding TypedTransaction failed");
1600 };
1601
1602 assert_eq!(
1603 tx.hash(),
1604 &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
1605 .parse::<B256>()
1606 .unwrap()
1607 );
1608 assert_eq!(
1609 tx.recover_signer().unwrap(),
1610 "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
1611 );
1612 }
1613
1614 #[test]
1617 fn test_decode_live_4844_tx() {
1618 use alloy_primitives::{address, b256};
1619
1620 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1622 let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1623 assert_eq!(res.r#type(), Some(3));
1624
1625 let tx = match res {
1626 TypedTransaction::EIP4844(tx) => tx,
1627 _ => unreachable!(),
1628 };
1629
1630 assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
1631
1632 assert_eq!(
1633 tx.tx().tx().blob_versioned_hashes,
1634 vec![
1635 b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1636 b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1637 b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1638 b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1639 b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1640 ]
1641 );
1642
1643 let from = tx.recover_signer().unwrap();
1644 assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1645 }
1646
1647 #[test]
1648 fn test_decode_encode_deposit_tx() {
1649 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
1651 .parse::<TxHash>()
1652 .unwrap();
1653
1654 let raw_tx = alloy_primitives::hex::decode(
1656 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
1657 )
1658 .unwrap();
1659 let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1660
1661 let mut encoded = Vec::new();
1662 dep_tx.encode_2718(&mut encoded);
1663
1664 assert_eq!(raw_tx, encoded);
1665
1666 assert_eq!(tx_hash, dep_tx.hash());
1667 }
1668
1669 #[test]
1670 fn can_recover_sender_not_normalized() {
1671 let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
1672
1673 let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1674 panic!("decoding TypedTransaction failed");
1675 };
1676
1677 assert_eq!(tx.tx().input, Bytes::from(b""));
1678 assert_eq!(tx.tx().gas_price, 1);
1679 assert_eq!(tx.tx().gas_limit, 21000);
1680 assert_eq!(tx.tx().nonce, 0);
1681 if let TxKind::Call(to) = tx.tx().to {
1682 assert_eq!(
1683 to,
1684 "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
1685 );
1686 } else {
1687 panic!("expected a call transaction");
1688 }
1689 assert_eq!(tx.tx().value, U256::from(0x0au64));
1690 assert_eq!(
1691 tx.recover_signer().unwrap(),
1692 "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
1693 );
1694 }
1695
1696 #[test]
1697 fn encode_legacy_receipt() {
1698 let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1699
1700 let mut data = vec![];
1701 let receipt = TypedReceipt::Legacy(ReceiptWithBloom {
1702 receipt: Receipt {
1703 status: false.into(),
1704 cumulative_gas_used: 0x1,
1705 logs: vec![Log {
1706 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1707 data: LogData::new_unchecked(
1708 vec![
1709 B256::from_str(
1710 "000000000000000000000000000000000000000000000000000000000000dead",
1711 )
1712 .unwrap(),
1713 B256::from_str(
1714 "000000000000000000000000000000000000000000000000000000000000beef",
1715 )
1716 .unwrap(),
1717 ],
1718 Bytes::from_str("0100ff").unwrap(),
1719 ),
1720 }],
1721 },
1722 logs_bloom: [0; 256].into(),
1723 });
1724
1725 receipt.encode(&mut data);
1726
1727 assert_eq!(receipt.length(), expected.len());
1729 assert_eq!(data, expected);
1730 }
1731
1732 #[test]
1733 fn decode_legacy_receipt() {
1734 let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1735
1736 let expected = TypedReceipt::Legacy(ReceiptWithBloom {
1737 receipt: Receipt {
1738 status: false.into(),
1739 cumulative_gas_used: 0x1,
1740 logs: vec![Log {
1741 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1742 data: LogData::new_unchecked(
1743 vec![
1744 B256::from_str(
1745 "000000000000000000000000000000000000000000000000000000000000dead",
1746 )
1747 .unwrap(),
1748 B256::from_str(
1749 "000000000000000000000000000000000000000000000000000000000000beef",
1750 )
1751 .unwrap(),
1752 ],
1753 Bytes::from_str("0100ff").unwrap(),
1754 ),
1755 }],
1756 },
1757 logs_bloom: [0; 256].into(),
1758 });
1759
1760 let receipt = TypedReceipt::decode(&mut &data[..]).unwrap();
1761
1762 assert_eq!(receipt, expected);
1763 }
1764
1765 #[test]
1766 fn deser_to_type_tx() {
1767 let tx = r#"
1768 {
1769 "EIP1559": {
1770 "chainId": "0x7a69",
1771 "nonce": "0x0",
1772 "gas": "0x5209",
1773 "maxFeePerGas": "0x77359401",
1774 "maxPriorityFeePerGas": "0x1",
1775 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1776 "value": "0x0",
1777 "accessList": [],
1778 "input": "0x",
1779 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
1780 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
1781 "yParity": "0x0",
1782 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
1783 }
1784 }"#;
1785
1786 let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap();
1787 }
1788}