Skip to main content

alloy_network_primitives/
traits.rs

1use crate::{BlockTransactions, InclusionInfo};
2use alloy_consensus::{BlockHeader, Transaction};
3use alloy_eips::BlockNumHash;
4use alloy_primitives::{Address, BlockHash, TxHash, B256};
5use alloy_serde::WithOtherFields;
6
7/// Error returned when a transaction failed.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct TransactionFailedError {
10    /// Hash of the failed transaction.
11    pub transaction_hash: TxHash,
12}
13
14impl core::fmt::Display for TransactionFailedError {
15    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16        write!(f, "Transaction {} failed", self.transaction_hash)
17    }
18}
19
20impl core::error::Error for TransactionFailedError {}
21
22/// Receipt JSON-RPC response.
23pub trait ReceiptResponse {
24    /// Address of the created contract, or `None` if the transaction was not a deployment.
25    fn contract_address(&self) -> Option<Address>;
26
27    /// Status of the transaction.
28    ///
29    /// ## Note
30    ///
31    /// Caution must be taken when using this method for deep-historical
32    /// receipts, as it may not accurately reflect the status of the
33    /// transaction. The transaction status is not knowable from the receipt
34    /// for transactions before [EIP-658].
35    ///
36    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
37    fn status(&self) -> bool;
38
39    /// Hash of the block this transaction was included within.
40    fn block_hash(&self) -> Option<BlockHash>;
41
42    /// Number of the block this transaction was included within.
43    fn block_number(&self) -> Option<u64>;
44
45    /// Returns the [`BlockNumHash`] of the block this transaction was mined in.
46    ///
47    /// Returns `None` if either component is absent, as is normally the case for a pending
48    /// transaction.
49    fn block_hash_num(&self) -> Option<BlockNumHash> {
50        Some(BlockNumHash::new(self.block_number()?, self.block_hash()?))
51    }
52
53    /// Transaction Hash.
54    fn transaction_hash(&self) -> TxHash;
55
56    /// Index within the block.
57    fn transaction_index(&self) -> Option<u64>;
58
59    /// Gas used by this transaction alone.
60    fn gas_used(&self) -> u64;
61
62    /// Effective gas price.
63    fn effective_gas_price(&self) -> u128;
64
65    /// Returns the execution-gas cost in wei: `gas_used * effective_gas_price`.
66    ///
67    /// This excludes blob-gas charges, transferred value, and network-specific fee components;
68    /// it is not the sender's total balance change. The ordinary `u128` multiplication can
69    /// overflow, panicking when overflow checks are enabled or wrapping otherwise.
70    fn cost(&self) -> u128 {
71        self.gas_used() as u128 * self.effective_gas_price()
72    }
73
74    /// Blob gas used by the eip-4844 transaction.
75    fn blob_gas_used(&self) -> Option<u64>;
76
77    /// Blob gas price paid by the eip-4844 transaction.
78    fn blob_gas_price(&self) -> Option<u128>;
79
80    /// Address of the sender.
81    fn from(&self) -> Address;
82
83    /// Address of the receiver, or `None` for contract creation.
84    fn to(&self) -> Option<Address>;
85
86    /// Returns the gas used in the block up to and including this transaction.
87    fn cumulative_gas_used(&self) -> u64;
88
89    /// Returns the post-transaction state root carried by pre-[EIP-658] receipts.
90    ///
91    /// [EIP-658] replaced this value with a status code, so post-Byzantium receipts normally
92    /// return `None`.
93    ///
94    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
95    fn state_root(&self) -> Option<B256>;
96
97    /// Ensures the transaction was successful, returning its hash in the error if it failed.
98    ///
99    /// This does not recover revert data and has the same pre-EIP-658 limitation as
100    /// [`Self::status`].
101    fn ensure_success(&self) -> Result<(), TransactionFailedError> {
102        if self.status() {
103            Ok(())
104        } else {
105            Err(TransactionFailedError { transaction_hash: self.transaction_hash() })
106        }
107    }
108}
109
110/// Transaction JSON-RPC response. Aggregates transaction data with its block and signer context.
111///
112/// The optional fee accessors split fixed-price and dynamic-fee consensus caps by transaction type
113/// and share names with differently typed methods on [`Transaction`]. Use trait-qualified calls
114/// such as `TransactionResponse::max_fee_per_gas(tx)` when the distinction matters.
115pub trait TransactionResponse: Transaction {
116    /// Hash of the transaction
117    #[doc(alias = "transaction_hash")]
118    fn tx_hash(&self) -> TxHash;
119
120    /// Returns the hash of the block this transaction was mined in.
121    ///
122    /// Returns `None` when absent from the response, normally for a pending transaction.
123    fn block_hash(&self) -> Option<BlockHash>;
124
125    /// Returns the number of the block this transaction was mined in.
126    ///
127    /// Returns `None` when absent from the response, normally for a pending transaction.
128    fn block_number(&self) -> Option<u64>;
129
130    /// Returns the [`BlockNumHash`] of the block this transaction was mined in.
131    ///
132    /// Returns `None` if either component is absent, as is normally the case for a pending
133    /// transaction.
134    fn block_hash_num(&self) -> Option<BlockNumHash> {
135        Some(BlockNumHash::new(self.block_number()?, self.block_hash()?))
136    }
137
138    /// Transaction Index
139    fn transaction_index(&self) -> Option<u64>;
140
141    /// Sender of the transaction
142    fn from(&self) -> Address;
143
144    /// Returns the fixed gas price for standard Ethereum transaction type IDs 0 and 1.
145    ///
146    /// The default returns the consensus fee cap for those type IDs and `None` for IDs 2 and
147    /// above. Networks with different type numbering or RPC `gasPrice` semantics must override
148    /// this method.
149    fn gas_price(&self) -> Option<u128> {
150        if self.ty() < 2 {
151            return Some(Transaction::max_fee_per_gas(self));
152        }
153        None
154    }
155
156    /// Returns the maximum fee per gas for standard Ethereum transaction type IDs 2 and above.
157    ///
158    /// The default returns `None` for type IDs 0 and 1 and the consensus fee cap for later IDs.
159    /// Networks with different type numbering must override this method.
160    fn max_fee_per_gas(&self) -> Option<u128> {
161        if self.ty() < 2 {
162            return None;
163        }
164        Some(Transaction::max_fee_per_gas(self))
165    }
166
167    /// Transaction type format for RPC. This field is included since eip-2930.
168    fn transaction_type(&self) -> Option<u8> {
169        match self.ty() {
170            0 => None,
171            ty => Some(ty),
172        }
173    }
174
175    /// Returns the [`InclusionInfo`] if the transaction has been included.
176    ///
177    /// Returns `None` if this transaction is still pending (missing block number, hash, or index).
178    fn inclusion_info(&self) -> Option<InclusionInfo> {
179        Some(InclusionInfo {
180            block_hash: self.block_hash()?,
181            block_number: self.block_number()?,
182            transaction_index: self.transaction_index()?,
183        })
184    }
185}
186
187/// Header JSON-RPC response.
188pub trait HeaderResponse: BlockHeader {
189    /// Block hash
190    fn hash(&self) -> BlockHash;
191
192    /// Returns the [`BlockNumHash`] of this header.
193    fn num_hash(&self) -> BlockNumHash {
194        BlockNumHash::new(self.number(), self.hash())
195    }
196}
197
198/// Block JSON-RPC response.
199pub trait BlockResponse {
200    /// Concrete RPC header representation.
201    type Header;
202    /// Full-transaction representation used by [`BlockTransactions::Full`].
203    type Transaction: TransactionResponse;
204
205    /// Block header
206    fn header(&self) -> &Self::Header;
207
208    /// Block transactions
209    fn transactions(&self) -> &BlockTransactions<Self::Transaction>;
210
211    /// Returns a mutable reference to the block transactions.
212    ///
213    /// Mutating transactions does not recompute or validate header fields such as the transaction
214    /// root.
215    fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction>;
216
217    /// Returns flattened chain- or client-specific RPC fields when they were retained.
218    ///
219    /// The default is `None`. A [`WithOtherFields`] response returns `Some` even when its map is
220    /// empty.
221    fn other_fields(&self) -> Option<&alloy_serde::OtherFields> {
222        None
223    }
224}
225
226impl<T: TransactionResponse> TransactionResponse for WithOtherFields<T> {
227    fn tx_hash(&self) -> TxHash {
228        self.inner.tx_hash()
229    }
230
231    fn block_hash(&self) -> Option<BlockHash> {
232        self.inner.block_hash()
233    }
234
235    fn block_number(&self) -> Option<u64> {
236        self.inner.block_number()
237    }
238
239    fn transaction_index(&self) -> Option<u64> {
240        self.inner.transaction_index()
241    }
242
243    fn from(&self) -> Address {
244        self.inner.from()
245    }
246}
247
248impl<T: ReceiptResponse> ReceiptResponse for WithOtherFields<T> {
249    fn contract_address(&self) -> Option<Address> {
250        self.inner.contract_address()
251    }
252
253    fn status(&self) -> bool {
254        self.inner.status()
255    }
256
257    fn block_hash(&self) -> Option<BlockHash> {
258        self.inner.block_hash()
259    }
260
261    fn block_number(&self) -> Option<u64> {
262        self.inner.block_number()
263    }
264
265    fn transaction_hash(&self) -> TxHash {
266        self.inner.transaction_hash()
267    }
268
269    fn transaction_index(&self) -> Option<u64> {
270        self.inner.transaction_index()
271    }
272
273    fn gas_used(&self) -> u64 {
274        self.inner.gas_used()
275    }
276
277    fn effective_gas_price(&self) -> u128 {
278        self.inner.effective_gas_price()
279    }
280
281    fn blob_gas_used(&self) -> Option<u64> {
282        self.inner.blob_gas_used()
283    }
284
285    fn blob_gas_price(&self) -> Option<u128> {
286        self.inner.blob_gas_price()
287    }
288
289    fn from(&self) -> Address {
290        self.inner.from()
291    }
292
293    fn to(&self) -> Option<Address> {
294        self.inner.to()
295    }
296
297    fn cumulative_gas_used(&self) -> u64 {
298        self.inner.cumulative_gas_used()
299    }
300
301    fn state_root(&self) -> Option<B256> {
302        self.inner.state_root()
303    }
304}
305
306impl<T: BlockResponse> BlockResponse for WithOtherFields<T> {
307    type Header = T::Header;
308    type Transaction = T::Transaction;
309
310    fn header(&self) -> &Self::Header {
311        self.inner.header()
312    }
313
314    fn transactions(&self) -> &BlockTransactions<Self::Transaction> {
315        self.inner.transactions()
316    }
317
318    fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
319        self.inner.transactions_mut()
320    }
321
322    fn other_fields(&self) -> Option<&alloy_serde::OtherFields> {
323        Some(&self.other)
324    }
325}
326
327impl<T: HeaderResponse> HeaderResponse for WithOtherFields<T> {
328    fn hash(&self) -> BlockHash {
329        self.inner.hash()
330    }
331}