alloy_network/any/
mod.rs

1mod builder;
2
3mod either;
4use alloy_consensus::TxEnvelope;
5use alloy_eips::{eip7702::SignedAuthorization, Typed2718};
6use alloy_primitives::{Bytes, ChainId, TxKind, B256, U256};
7pub use either::{AnyTxEnvelope, AnyTypedTransaction};
8
9mod unknowns;
10pub use unknowns::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction};
11
12pub use alloy_consensus_any::{AnyHeader, AnyReceiptEnvelope};
13
14use crate::Network;
15use alloy_consensus::{
16    error::ValueError,
17    transaction::{Either, Recovered},
18};
19use alloy_network_primitives::{BlockResponse, TransactionResponse};
20pub use alloy_rpc_types_any::{AnyRpcHeader, AnyTransactionReceipt};
21use alloy_rpc_types_eth::{AccessList, Block, BlockTransactions, Transaction, TransactionRequest};
22use alloy_serde::WithOtherFields;
23use derive_more::From;
24use serde::{Deserialize, Serialize};
25use std::ops::{Deref, DerefMut};
26
27/// Types for a catch-all network.
28///
29/// `AnyNetwork`'s associated types allow for many different types of
30/// transactions, using catch-all fields. This [`Network`] should be used
31/// only when the application needs to support multiple networks via the same
32/// codepaths without knowing the networks at compile time.
33///
34/// ## Rough Edges
35///
36/// Supporting arbitrary unknown types is hard, and users of this network
37/// should be aware of the following:
38///
39/// - The implementation of [`Decodable2718`] for [`AnyTxEnvelope`] will not work for non-Ethereum
40///   transaction types. It will succesfully decode an Ethereum [`TxEnvelope`], but will decode only
41///   the type for any unknown transaction type. It will also leave the buffer unconsumed, which
42///   will cause further deserialization to produce erroneous results.
43/// - The implementation of [`Encodable2718`] for [`AnyTypedTransaction`] will not work for
44///   non-Ethereum transaction types. It will encode the type for any unknown transaction type, but
45///   will not encode any other fields. This is symmetric with the decoding behavior, but still
46///   erroneous.
47/// - The [`TransactionRequest`] will build ONLY Ethereum types. It will error when attempting to
48///   build any unknown type.
49/// - The [`Network::TransactionResponse`] may deserialize unknown metadata fields into the inner
50///   [`AnyTxEnvelope`], rather than into the outer [`WithOtherFields`].
51///
52/// [`Decodable2718`]: alloy_eips::eip2718::Decodable2718
53/// [`Encodable2718`]: alloy_eips::eip2718::Encodable2718
54/// [`TxEnvelope`]: alloy_consensus::TxEnvelope
55#[derive(Clone, Copy, Debug)]
56pub struct AnyNetwork {
57    _private: (),
58}
59
60impl Network for AnyNetwork {
61    type TxType = AnyTxType;
62
63    type TxEnvelope = AnyTxEnvelope;
64
65    type UnsignedTx = AnyTypedTransaction;
66
67    type ReceiptEnvelope = AnyReceiptEnvelope;
68
69    type Header = AnyHeader;
70
71    type TransactionRequest = WithOtherFields<TransactionRequest>;
72
73    type TransactionResponse = AnyRpcTransaction;
74
75    type ReceiptResponse = AnyTransactionReceipt;
76
77    type HeaderResponse = AnyRpcHeader;
78
79    type BlockResponse = AnyRpcBlock;
80}
81
82/// A wrapper for [`AnyRpcBlock`] that allows for handling unknown block types.
83///
84/// This type wraps:
85///  - rpc transaction
86///  - additional fields
87#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
88pub struct AnyRpcBlock(pub WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>);
89
90impl AnyRpcBlock {
91    /// Create a new [`AnyRpcBlock`].
92    pub fn new(inner: WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>) -> Self {
93        Self(inner)
94    }
95
96    /// Consumes the type and returns the wrapped rpc block.
97    pub fn into_inner(self) -> Block<AnyRpcTransaction, AnyRpcHeader> {
98        self.0.into_inner()
99    }
100
101    /// Tries to convert inner transactions into a vector of [`AnyRpcTransaction`].
102    ///
103    /// Returns an error if the block contains only transaction hashes or if it is an uncle block.
104    pub fn try_into_transactions(
105        self,
106    ) -> Result<Vec<AnyRpcTransaction>, ValueError<BlockTransactions<AnyRpcTransaction>>> {
107        self.0.inner.try_into_transactions()
108    }
109
110    /// Consumes the type and returns an iterator over the transactions in this block
111    pub fn into_transactions_iter(self) -> impl Iterator<Item = AnyRpcTransaction> {
112        self.into_inner().transactions.into_transactions()
113    }
114}
115
116impl BlockResponse for AnyRpcBlock {
117    type Header = AnyRpcHeader;
118    type Transaction = AnyRpcTransaction;
119
120    fn header(&self) -> &Self::Header {
121        &self.0.inner.header
122    }
123
124    fn transactions(&self) -> &BlockTransactions<Self::Transaction> {
125        &self.0.inner.transactions
126    }
127
128    fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
129        &mut self.0.inner.transactions
130    }
131
132    fn other_fields(&self) -> Option<&alloy_serde::OtherFields> {
133        self.0.other_fields()
134    }
135}
136
137impl AsRef<WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>> for AnyRpcBlock {
138    fn as_ref(&self) -> &WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
139        &self.0
140    }
141}
142
143impl Deref for AnyRpcBlock {
144    type Target = WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>;
145
146    fn deref(&self) -> &Self::Target {
147        &self.0
148    }
149}
150
151impl DerefMut for AnyRpcBlock {
152    fn deref_mut(&mut self) -> &mut Self::Target {
153        &mut self.0
154    }
155}
156
157impl From<Block> for AnyRpcBlock {
158    fn from(value: Block) -> Self {
159        let block = value
160            .map_header(|h| h.map(|h| alloy_consensus_any::AnyHeader { ..h.into() }))
161            .map_transactions(|tx| {
162                AnyRpcTransaction::new(WithOtherFields::new(tx.map(AnyTxEnvelope::Ethereum)))
163            });
164
165        Self(WithOtherFields::new(block))
166    }
167}
168
169impl From<AnyRpcBlock> for Block<AnyRpcTransaction, AnyRpcHeader> {
170    fn from(value: AnyRpcBlock) -> Self {
171        value.into_inner()
172    }
173}
174impl From<AnyRpcBlock> for WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
175    fn from(value: AnyRpcBlock) -> Self {
176        value.0
177    }
178}
179
180/// A wrapper for [`AnyRpcTransaction`] that allows for handling unknown transaction types.
181#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
182pub struct AnyRpcTransaction(pub WithOtherFields<Transaction<AnyTxEnvelope>>);
183
184impl AnyRpcTransaction {
185    /// Create a new [`AnyRpcTransaction`].
186    pub fn new(inner: WithOtherFields<Transaction<AnyTxEnvelope>>) -> Self {
187        Self(inner)
188    }
189
190    /// Split the transaction into its parts.
191    pub fn into_parts(self) -> (Transaction<AnyTxEnvelope>, alloy_serde::OtherFields) {
192        let WithOtherFields { inner, other } = self.0;
193        (inner, other)
194    }
195
196    /// Consumes the outer layer for this transaction and returns the inner transaction.
197    pub fn into_inner(self) -> Transaction<AnyTxEnvelope> {
198        self.0.into_inner()
199    }
200
201    /// Returns the inner transaction [`TxEnvelope`] if inner tx type if
202    /// [`AnyTxEnvelope::Ethereum`].
203    pub fn as_envelope(&self) -> Option<&TxEnvelope> {
204        self.inner.inner.as_envelope()
205    }
206
207    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
208    /// If the transaction is not an Ethereum transaction, it is returned as an error.
209    pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<AnyTxEnvelope>> {
210        self.0.inner.inner.into_inner().try_into_envelope()
211    }
212
213    /// Attempts to convert the [`AnyRpcTransaction`] into `Either::Right` if this is an unknown
214    /// variant.
215    ///
216    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
217    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
218    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
219    where
220        T: TryFrom<Self>,
221    {
222        if self.0.inner.inner.inner().is_ethereum() {
223            Ok(Either::Left(self.0.inner.inner.into_inner().try_into_envelope().unwrap()))
224        } else {
225            T::try_from(self).map(Either::Right)
226        }
227    }
228
229    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an unknown
230    /// variant.
231    ///
232    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
233    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
234    pub fn try_unknown_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
235    where
236        T: TryFrom<UnknownTxEnvelope>,
237    {
238        self.0.inner.inner.into_inner().try_into_either()
239    }
240
241    /// Applies the given closure to the inner transaction type.
242    ///
243    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
244    /// Applies the given closure to the inner transaction type.
245    pub fn map<Tx>(self, f: impl FnOnce(AnyTxEnvelope) -> Tx) -> Transaction<Tx> {
246        self.into_inner().map(f)
247    }
248
249    /// Applies the given fallible closure to the inner transactions.
250    ///
251    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
252    pub fn try_map<Tx, E>(
253        self,
254        f: impl FnOnce(AnyTxEnvelope) -> Result<Tx, E>,
255    ) -> Result<Transaction<Tx>, E> {
256        self.into_inner().try_map(f)
257    }
258
259    /// Converts the transaction type to the given alternative that is `From<T>`.
260    ///
261    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
262    pub fn convert<U>(self) -> Transaction<U>
263    where
264        U: From<AnyTxEnvelope>,
265    {
266        self.into_inner().map(U::from)
267    }
268
269    /// Converts the transaction to the given alternative that is `TryFrom<T>`
270    ///
271    /// Returns the transaction with the new transaction type if all conversions were successful.
272    ///
273    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
274    pub fn try_convert<U>(self) -> Result<Transaction<U>, U::Error>
275    where
276        U: TryFrom<AnyTxEnvelope>,
277    {
278        self.into_inner().try_map(U::try_from)
279    }
280}
281
282impl AsRef<AnyTxEnvelope> for AnyRpcTransaction {
283    fn as_ref(&self) -> &AnyTxEnvelope {
284        &self.0.inner.inner
285    }
286}
287
288impl Deref for AnyRpcTransaction {
289    type Target = WithOtherFields<Transaction<AnyTxEnvelope>>;
290
291    fn deref(&self) -> &Self::Target {
292        &self.0
293    }
294}
295
296impl DerefMut for AnyRpcTransaction {
297    fn deref_mut(&mut self) -> &mut Self::Target {
298        &mut self.0
299    }
300}
301
302impl From<Transaction<TxEnvelope>> for AnyRpcTransaction {
303    fn from(tx: Transaction<TxEnvelope>) -> Self {
304        let tx = tx.map(AnyTxEnvelope::Ethereum);
305        Self(WithOtherFields::new(tx))
306    }
307}
308
309impl From<AnyRpcTransaction> for AnyTxEnvelope {
310    fn from(tx: AnyRpcTransaction) -> Self {
311        tx.0.inner.into_inner()
312    }
313}
314
315impl From<AnyRpcTransaction> for Transaction<AnyTxEnvelope> {
316    fn from(tx: AnyRpcTransaction) -> Self {
317        tx.0.inner
318    }
319}
320
321impl From<AnyRpcTransaction> for WithOtherFields<Transaction<AnyTxEnvelope>> {
322    fn from(tx: AnyRpcTransaction) -> Self {
323        tx.0
324    }
325}
326
327impl From<AnyRpcTransaction> for Recovered<AnyTxEnvelope> {
328    fn from(tx: AnyRpcTransaction) -> Self {
329        tx.0.inner.inner
330    }
331}
332
333impl TryFrom<AnyRpcTransaction> for TxEnvelope {
334    type Error = ValueError<AnyTxEnvelope>;
335
336    fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
337        value.try_into_envelope()
338    }
339}
340
341impl alloy_consensus::Transaction for AnyRpcTransaction {
342    fn chain_id(&self) -> Option<ChainId> {
343        self.inner.chain_id()
344    }
345
346    fn nonce(&self) -> u64 {
347        self.inner.nonce()
348    }
349
350    fn gas_limit(&self) -> u64 {
351        self.inner.gas_limit()
352    }
353
354    fn gas_price(&self) -> Option<u128> {
355        alloy_consensus::Transaction::gas_price(&self.0.inner)
356    }
357
358    fn max_fee_per_gas(&self) -> u128 {
359        alloy_consensus::Transaction::max_fee_per_gas(&self.inner)
360    }
361
362    fn max_priority_fee_per_gas(&self) -> Option<u128> {
363        self.inner.max_priority_fee_per_gas()
364    }
365
366    fn max_fee_per_blob_gas(&self) -> Option<u128> {
367        self.inner.max_fee_per_blob_gas()
368    }
369
370    fn priority_fee_or_price(&self) -> u128 {
371        self.inner.priority_fee_or_price()
372    }
373
374    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
375        self.inner.effective_gas_price(base_fee)
376    }
377
378    fn is_dynamic_fee(&self) -> bool {
379        self.inner.is_dynamic_fee()
380    }
381
382    fn kind(&self) -> TxKind {
383        self.inner.kind()
384    }
385
386    fn is_create(&self) -> bool {
387        self.inner.is_create()
388    }
389
390    fn value(&self) -> U256 {
391        self.inner.value()
392    }
393
394    fn input(&self) -> &Bytes {
395        self.inner.input()
396    }
397
398    fn access_list(&self) -> Option<&AccessList> {
399        self.inner.access_list()
400    }
401
402    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
403        self.inner.blob_versioned_hashes()
404    }
405
406    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
407        self.inner.authorization_list()
408    }
409}
410
411impl TransactionResponse for AnyRpcTransaction {
412    fn tx_hash(&self) -> alloy_primitives::TxHash {
413        self.inner.tx_hash()
414    }
415
416    fn block_hash(&self) -> Option<alloy_primitives::BlockHash> {
417        self.0.inner.block_hash
418    }
419
420    fn block_number(&self) -> Option<u64> {
421        self.inner.block_number
422    }
423
424    fn transaction_index(&self) -> Option<u64> {
425        self.inner.transaction_index
426    }
427
428    fn from(&self) -> alloy_primitives::Address {
429        self.inner.from()
430    }
431
432    fn gas_price(&self) -> Option<u128> {
433        self.inner.effective_gas_price
434    }
435}
436
437impl Typed2718 for AnyRpcTransaction {
438    fn ty(&self) -> u8 {
439        self.inner.ty()
440    }
441}