1use alloy_consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom, TxReceipt, Typed2718};
4use alloy_primitives::{Address, Bloom, TxKind};
5use alloy_rpc_types_eth::TransactionReceipt;
6use alloy_serde::WithOtherFields;
7use arb_primitives::ArbPrimitives;
8use reth_primitives_traits::SealedBlock;
9use reth_rpc_convert::transaction::{ConvertReceiptInput, ReceiptConverter};
10use reth_rpc_eth_types::EthApiError;
11
12use crate::header::l1_block_number_from_mix_hash;
13
14#[derive(Debug, Clone)]
16pub struct ArbReceiptConverter;
17
18impl ReceiptConverter<ArbPrimitives> for ArbReceiptConverter {
19 type RpcReceipt = WithOtherFields<TransactionReceipt>;
20 type Error = EthApiError;
21
22 fn convert_receipts(
23 &self,
24 receipts: Vec<ConvertReceiptInput<'_, ArbPrimitives>>,
25 ) -> Result<Vec<Self::RpcReceipt>, EthApiError> {
26 let results = receipts
27 .into_iter()
28 .map(|input| convert_single_receipt(input, None))
29 .collect();
30 Ok(results)
31 }
32
33 fn convert_receipts_with_block(
34 &self,
35 receipts: Vec<ConvertReceiptInput<'_, ArbPrimitives>>,
36 block: &SealedBlock<alloy_consensus::Block<arb_primitives::ArbTransactionSigned>>,
37 ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
38 let mix_hash = block.header().mix_hash;
39 let l1_block_number = l1_block_number_from_mix_hash(&mix_hash);
40
41 let results = receipts
42 .into_iter()
43 .map(|input| convert_single_receipt(input, Some(l1_block_number)))
44 .collect();
45 Ok(results)
46 }
47}
48
49fn convert_single_receipt(
50 input: ConvertReceiptInput<'_, ArbPrimitives>,
51 l1_block_number: Option<u64>,
52) -> WithOtherFields<TransactionReceipt> {
53 use alloy_consensus::{transaction::TxHashRef, Transaction};
54
55 let ConvertReceiptInput {
56 receipt,
57 tx,
58 gas_used,
59 next_log_index,
60 meta,
61 } = input;
62
63 let from = tx.signer();
64 let tx_hash = *tx.tx_hash();
65 let tx_type = tx.ty();
66
67 let (contract_address, to) = match tx.kind() {
68 TxKind::Create => (Some(from.create(tx.nonce())), None),
69 TxKind::Call(addr) => (None, Some(Address(*addr))),
70 };
71
72 let cumulative_gas_used = receipt.cumulative_gas_used();
73 let status = receipt.status_or_post_state();
74 let gas_used_for_l1 = receipt.gas_used_for_l1;
75
76 let rpc_logs: Vec<alloy_rpc_types_eth::Log> = receipt
78 .logs()
79 .iter()
80 .enumerate()
81 .map(|(i, log)| alloy_rpc_types_eth::Log {
82 inner: log.clone(),
83 block_hash: Some(meta.block_hash),
84 block_number: Some(meta.block_number),
85 block_timestamp: None,
86 transaction_hash: Some(tx_hash),
87 transaction_index: Some(meta.index),
88 log_index: Some(next_log_index as u64 + i as u64),
89 removed: false,
90 })
91 .collect();
92
93 let bloom: Bloom = receipt.logs().iter().collect();
94
95 let receipt_with_bloom = ReceiptWithBloom::new(
96 Receipt {
97 status,
98 cumulative_gas_used,
99 logs: rpc_logs,
100 },
101 bloom,
102 );
103
104 let envelope = match tx_type {
106 0x01 => ReceiptEnvelope::Eip2930(receipt_with_bloom),
107 0x02 => ReceiptEnvelope::Eip1559(receipt_with_bloom),
108 0x03 => ReceiptEnvelope::Eip4844(receipt_with_bloom),
109 0x04 => ReceiptEnvelope::Eip7702(receipt_with_bloom),
110 _ => ReceiptEnvelope::Legacy(receipt_with_bloom),
111 };
112
113 let effective_gas_price = meta.base_fee.unwrap_or(0) as u128;
115
116 let base_receipt = TransactionReceipt {
117 inner: envelope,
118 transaction_hash: tx_hash,
119 transaction_index: Some(meta.index),
120 block_hash: Some(meta.block_hash),
121 block_number: Some(meta.block_number),
122 gas_used,
123 effective_gas_price,
124 blob_gas_used: None,
125 blob_gas_price: None,
126 from,
127 to,
128 contract_address,
129 };
130
131 let mut other = std::collections::BTreeMap::new();
133
134 if tx_type >= 0x64 {
137 other.insert(
138 "type".to_string(),
139 serde_json::to_value(format!("{tx_type:#x}")).unwrap_or_default(),
140 );
141 }
142
143 other.insert(
145 "gasUsedForL1".to_string(),
146 serde_json::to_value(format!("{:#x}", gas_used_for_l1)).unwrap_or_default(),
147 );
148
149 if let Some(l1_bn) = l1_block_number {
151 other.insert(
152 "l1BlockNumber".to_string(),
153 serde_json::to_value(format!("{l1_bn:#x}")).unwrap_or_default(),
154 );
155 }
156
157 if !receipt.multi_gas_used.is_zero() {
159 other.insert(
160 "multiGasUsed".to_string(),
161 serde_json::to_value(receipt.multi_gas_used).unwrap_or_default(),
162 );
163 }
164
165 WithOtherFields {
166 inner: base_receipt,
167 other: alloy_serde::OtherFields::new(other),
168 }
169}