Skip to main content

alloy_network/any/
mod.rs

1mod builder;
2mod either;
3
4pub mod error;
5
6use alloy_consensus::{
7    Sealed, Signed, TxEip1559, TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy,
8};
9use alloy_eips::{eip7702::SignedAuthorization, Typed2718};
10use alloy_primitives::{Bytes, ChainId, TxKind, B256, U256};
11pub use either::{AnyTxEnvelope, AnyTypedTransaction};
12use std::error::Error;
13
14mod unknowns;
15pub use unknowns::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction};
16
17pub use alloy_consensus_any::{AnyHeader, AnyReceiptEnvelope};
18
19use crate::{any::error::AnyConversionError, Network};
20use alloy_consensus::{
21    error::ValueError,
22    transaction::{Either, Recovered},
23};
24use alloy_network_primitives::{BlockResponse, TransactionResponse};
25pub use alloy_rpc_types_any::{AnyRpcHeader, AnyTransactionReceipt};
26use alloy_rpc_types_eth::{AccessList, Block, BlockTransactions, Transaction, TransactionRequest};
27use alloy_serde::WithOtherFields;
28use derive_more::From;
29use serde::{Deserialize, Serialize};
30use std::ops::{Deref, DerefMut};
31
32/// Types for a catch-all network.
33///
34/// `AnyNetwork`'s associated types allow for many different types of
35/// transactions, using catch-all fields. This [`Network`] should be used
36/// only when the application needs to support multiple networks via the same
37/// codepaths without knowing the networks at compile time.
38///
39/// ## Rough Edges
40///
41/// Supporting arbitrary unknown types is hard, and users of this network
42/// should be aware of the following:
43///
44/// - The implementation of [`Decodable2718`] for [`AnyTxEnvelope`] will not work for non-Ethereum
45///   transaction types. It will successfully decode an Ethereum [`TxEnvelope`], but will decode
46///   only the type for any unknown transaction type. It will also leave the buffer unconsumed,
47///   which will cause further deserialization to produce erroneous results.
48/// - The implementation of [`Encodable2718`] for [`AnyTxEnvelope`] will panic for non-Ethereum
49///   transaction types. Unknown transaction types cannot be re-encoded through [`AnyNetwork`]; use
50///   a custom transaction type and network implementation instead.
51/// - The [`TransactionRequest`] will build ONLY Ethereum types. It will error when attempting to
52///   build any unknown type.
53/// - The [`Network::TransactionResponse`] may deserialize unknown metadata fields into the inner
54///   [`AnyTxEnvelope`], rather than into the outer [`WithOtherFields`].
55///
56/// [`Decodable2718`]: alloy_eips::eip2718::Decodable2718
57/// [`Encodable2718`]: alloy_eips::eip2718::Encodable2718
58/// [`TxEnvelope`]: alloy_consensus::TxEnvelope
59#[derive(Clone, Copy, Debug)]
60pub struct AnyNetwork {
61    _private: (),
62}
63
64impl Network for AnyNetwork {
65    type TxType = AnyTxType;
66
67    type TxEnvelope = AnyTxEnvelope;
68
69    type UnsignedTx = AnyTypedTransaction;
70
71    type ReceiptEnvelope = AnyReceiptEnvelope;
72
73    type Header = AnyHeader;
74
75    type TransactionRequest = WithOtherFields<TransactionRequest>;
76
77    type TransactionResponse = AnyRpcTransaction;
78
79    type ReceiptResponse = AnyTransactionReceipt;
80
81    type HeaderResponse = AnyRpcHeader;
82
83    type BlockResponse = AnyRpcBlock;
84}
85
86/// A wrapper for [`AnyRpcBlock`] that allows for handling unknown block types.
87///
88/// This type wraps:
89///  - rpc transaction
90///  - additional fields
91#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
92pub struct AnyRpcBlock(pub WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>);
93
94impl AnyRpcBlock {
95    /// Create a new [`AnyRpcBlock`].
96    pub const fn new(inner: WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>) -> Self {
97        Self(inner)
98    }
99
100    /// Consumes the type and returns the wrapped rpc block.
101    pub fn into_inner(self) -> Block<AnyRpcTransaction, AnyRpcHeader> {
102        self.0.into_inner()
103    }
104
105    /// Consumes the type and returns the block header with the block's additional fields.
106    pub fn into_header_with_other(self) -> WithOtherFields<AnyRpcHeader> {
107        let WithOtherFields { inner, other } = self.0;
108        WithOtherFields { inner: inner.header, other }
109    }
110
111    /// Attempts to convert the inner RPC [`Block`] into a consensus block.
112    ///
113    /// Returns an [`AnyConversionError`] if any of the conversions fail.
114    pub fn try_into_consensus<T, H>(
115        self,
116    ) -> Result<alloy_consensus::Block<T, H>, AnyConversionError>
117    where
118        T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
119        H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
120    {
121        self.into_inner()
122            .map_header(|h| h.into_consensus())
123            .try_convert_header()
124            .map_err(AnyConversionError::new)?
125            .try_convert_transactions()
126            .map_err(AnyConversionError::new)
127            .map(Block::into_consensus_block)
128    }
129
130    /// Attempts to convert the inner RPC [`Block`] into a sealed consensus block.
131    ///
132    /// Uses the block hash from the RPC header to seal the block.
133    ///
134    /// Returns an [`AnyConversionError`] if any of the conversions fail.
135    pub fn try_into_sealed<T, H>(
136        self,
137    ) -> Result<Sealed<alloy_consensus::Block<T, H>>, AnyConversionError>
138    where
139        T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
140        H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
141    {
142        let block_hash = self.header.hash;
143        let block = self.try_into_consensus()?;
144        Ok(Sealed::new_unchecked(block, block_hash))
145    }
146
147    /// Tries to convert inner transactions into a vector of [`AnyRpcTransaction`].
148    ///
149    /// Returns an error if the block contains only transaction hashes or if it is an uncle block.
150    pub fn try_into_transactions(
151        self,
152    ) -> Result<Vec<AnyRpcTransaction>, ValueError<BlockTransactions<AnyRpcTransaction>>> {
153        self.0.inner.try_into_transactions()
154    }
155
156    /// Consumes the type and returns an iterator over the transactions in this block
157    pub fn into_transactions_iter(self) -> impl Iterator<Item = AnyRpcTransaction> {
158        self.into_inner().transactions.into_transactions()
159    }
160}
161
162impl BlockResponse for AnyRpcBlock {
163    type Header = AnyRpcHeader;
164    type Transaction = AnyRpcTransaction;
165
166    fn header(&self) -> &Self::Header {
167        &self.0.inner.header
168    }
169
170    fn transactions(&self) -> &BlockTransactions<Self::Transaction> {
171        &self.0.inner.transactions
172    }
173
174    fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
175        &mut self.0.inner.transactions
176    }
177
178    fn other_fields(&self) -> Option<&alloy_serde::OtherFields> {
179        self.0.other_fields()
180    }
181}
182
183impl AsRef<WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>> for AnyRpcBlock {
184    fn as_ref(&self) -> &WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
185        &self.0
186    }
187}
188
189impl Deref for AnyRpcBlock {
190    type Target = WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>;
191
192    fn deref(&self) -> &Self::Target {
193        &self.0
194    }
195}
196
197impl DerefMut for AnyRpcBlock {
198    fn deref_mut(&mut self) -> &mut Self::Target {
199        &mut self.0
200    }
201}
202
203impl From<Block> for AnyRpcBlock {
204    fn from(value: Block) -> Self {
205        let block = value.map_header(|h| h.map(|h| h.into())).map_transactions(|tx| {
206            AnyRpcTransaction::new(WithOtherFields::new(tx.map(AnyTxEnvelope::Ethereum)))
207        });
208
209        Self(WithOtherFields::new(block))
210    }
211}
212
213impl From<AnyRpcBlock> for Block<AnyRpcTransaction, AnyRpcHeader> {
214    fn from(value: AnyRpcBlock) -> Self {
215        value.into_inner()
216    }
217}
218impl From<AnyRpcBlock> for WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
219    fn from(value: AnyRpcBlock) -> Self {
220        value.0
221    }
222}
223
224impl<T, H> TryFrom<AnyRpcBlock> for alloy_consensus::Block<T, H>
225where
226    T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
227    H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
228{
229    type Error = AnyConversionError;
230
231    fn try_from(value: AnyRpcBlock) -> Result<Self, Self::Error> {
232        value.try_into_consensus()
233    }
234}
235
236/// A wrapper for [`AnyRpcTransaction`] that allows for handling unknown transaction types.
237#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
238pub struct AnyRpcTransaction(pub WithOtherFields<Transaction<AnyTxEnvelope>>);
239
240impl AnyRpcTransaction {
241    /// Create a new [`AnyRpcTransaction`].
242    pub const fn new(inner: WithOtherFields<Transaction<AnyTxEnvelope>>) -> Self {
243        Self(inner)
244    }
245
246    /// Split the transaction into its parts.
247    pub fn into_parts(self) -> (Transaction<AnyTxEnvelope>, alloy_serde::OtherFields) {
248        let WithOtherFields { inner, other } = self.0;
249        (inner, other)
250    }
251
252    /// Consumes the outer layer for this transaction and returns the inner transaction.
253    pub fn into_inner(self) -> Transaction<AnyTxEnvelope> {
254        self.0.into_inner()
255    }
256
257    /// Returns the inner transaction [`TxEnvelope`] if inner tx type if
258    /// [`AnyTxEnvelope::Ethereum`].
259    pub fn as_envelope(&self) -> Option<&TxEnvelope> {
260        self.inner.inner.as_envelope()
261    }
262
263    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
264    /// If the transaction is not an Ethereum transaction, it is returned as an error.
265    pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<AnyTxEnvelope>> {
266        self.0.inner.inner.into_inner().try_into_envelope()
267    }
268
269    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
270    pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
271        self.0.inner().inner.as_legacy()
272    }
273
274    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
275    pub fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
276        self.0.inner().inner.as_eip2930()
277    }
278
279    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
280    pub fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
281        self.0.inner().inner.as_eip1559()
282    }
283
284    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
285    pub fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
286        self.0.inner().inner.as_eip4844()
287    }
288
289    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
290    pub fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
291        self.0.inner().inner.as_eip7702()
292    }
293
294    /// Returns true if the transaction is a legacy transaction.
295    #[inline]
296    pub fn is_legacy(&self) -> bool {
297        self.0.inner().inner.is_legacy()
298    }
299
300    /// Returns true if the transaction is an EIP-2930 transaction.
301    #[inline]
302    pub fn is_eip2930(&self) -> bool {
303        self.0.inner().inner.is_eip2930()
304    }
305
306    /// Returns true if the transaction is an EIP-1559 transaction.
307    #[inline]
308    pub fn is_eip1559(&self) -> bool {
309        self.0.inner().inner.is_eip1559()
310    }
311
312    /// Returns true if the transaction is an EIP-4844 transaction.
313    #[inline]
314    pub fn is_eip4844(&self) -> bool {
315        self.0.inner().inner.is_eip4844()
316    }
317
318    /// Returns true if the transaction is an EIP-7702 transaction.
319    #[inline]
320    pub fn is_eip7702(&self) -> bool {
321        self.0.inner().inner.is_eip7702()
322    }
323
324    /// Attempts to convert the [`AnyRpcTransaction`] into `Either::Right` if this is an unknown
325    /// variant.
326    ///
327    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
328    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
329    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
330    where
331        T: TryFrom<Self>,
332    {
333        if self.0.inner.inner.inner().is_ethereum() {
334            Ok(Either::Left(self.0.inner.inner.into_inner().try_into_envelope().unwrap()))
335        } else {
336            T::try_from(self).map(Either::Right)
337        }
338    }
339
340    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an unknown
341    /// variant.
342    ///
343    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
344    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
345    pub fn try_unknown_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
346    where
347        T: TryFrom<UnknownTxEnvelope>,
348    {
349        self.0.inner.inner.into_inner().try_into_either()
350    }
351
352    /// Applies the given closure to the inner transaction type.
353    ///
354    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
355    /// Applies the given closure to the inner transaction type.
356    pub fn map<Tx>(self, f: impl FnOnce(AnyTxEnvelope) -> Tx) -> Transaction<Tx> {
357        self.into_inner().map(f)
358    }
359
360    /// Applies the given fallible closure to the inner transactions.
361    ///
362    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
363    pub fn try_map<Tx, E>(
364        self,
365        f: impl FnOnce(AnyTxEnvelope) -> Result<Tx, E>,
366    ) -> Result<Transaction<Tx>, E> {
367        self.into_inner().try_map(f)
368    }
369
370    /// Converts the transaction type to the given alternative that is `From<T>`.
371    ///
372    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
373    pub fn convert<U>(self) -> Transaction<U>
374    where
375        U: From<AnyTxEnvelope>,
376    {
377        self.map(U::from)
378    }
379
380    /// Converts the transaction to the given alternative that is `TryFrom<T>`
381    ///
382    /// Returns the transaction with the new transaction type if all conversions were successful.
383    ///
384    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
385    pub fn try_convert<U>(self) -> Result<Transaction<U>, U::Error>
386    where
387        U: TryFrom<AnyTxEnvelope>,
388    {
389        self.try_map(U::try_from)
390    }
391}
392
393impl AsRef<AnyTxEnvelope> for AnyRpcTransaction {
394    fn as_ref(&self) -> &AnyTxEnvelope {
395        &self.0.inner.inner
396    }
397}
398
399impl Deref for AnyRpcTransaction {
400    type Target = WithOtherFields<Transaction<AnyTxEnvelope>>;
401
402    fn deref(&self) -> &Self::Target {
403        &self.0
404    }
405}
406
407impl DerefMut for AnyRpcTransaction {
408    fn deref_mut(&mut self) -> &mut Self::Target {
409        &mut self.0
410    }
411}
412
413impl From<Transaction<TxEnvelope>> for AnyRpcTransaction {
414    fn from(tx: Transaction<TxEnvelope>) -> Self {
415        let tx = tx.map(AnyTxEnvelope::Ethereum);
416        Self(WithOtherFields::new(tx))
417    }
418}
419
420impl From<AnyRpcTransaction> for AnyTxEnvelope {
421    fn from(tx: AnyRpcTransaction) -> Self {
422        tx.0.inner.into_inner()
423    }
424}
425
426impl From<AnyRpcTransaction> for Transaction<AnyTxEnvelope> {
427    fn from(tx: AnyRpcTransaction) -> Self {
428        tx.0.inner
429    }
430}
431
432impl From<AnyRpcTransaction> for WithOtherFields<Transaction<AnyTxEnvelope>> {
433    fn from(tx: AnyRpcTransaction) -> Self {
434        tx.0
435    }
436}
437
438impl From<AnyRpcTransaction> for Recovered<AnyTxEnvelope> {
439    fn from(tx: AnyRpcTransaction) -> Self {
440        tx.0.inner.inner
441    }
442}
443
444impl From<AnyRpcTransaction> for WithOtherFields<TransactionRequest> {
445    fn from(tx: AnyRpcTransaction) -> Self {
446        let (inner, other) = tx.into_parts();
447        let (envelope, from) = inner.into_recovered().into_parts();
448        let mut req: Self = envelope.into();
449        req.inner.from = Some(from);
450        req.other.extend(other);
451        req
452    }
453}
454
455impl TryFrom<AnyRpcTransaction> for TxEnvelope {
456    type Error = ValueError<AnyTxEnvelope>;
457
458    fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
459        value.try_into_envelope()
460    }
461}
462
463impl alloy_consensus::Transaction for AnyRpcTransaction {
464    fn chain_id(&self) -> Option<ChainId> {
465        self.inner.chain_id()
466    }
467
468    fn nonce(&self) -> u64 {
469        self.inner.nonce()
470    }
471
472    fn gas_limit(&self) -> u64 {
473        self.inner.gas_limit()
474    }
475
476    fn gas_price(&self) -> Option<u128> {
477        alloy_consensus::Transaction::gas_price(&self.0.inner)
478    }
479
480    fn max_fee_per_gas(&self) -> u128 {
481        alloy_consensus::Transaction::max_fee_per_gas(&self.inner)
482    }
483
484    fn max_priority_fee_per_gas(&self) -> Option<u128> {
485        self.inner.max_priority_fee_per_gas()
486    }
487
488    fn max_fee_per_blob_gas(&self) -> Option<u128> {
489        self.inner.max_fee_per_blob_gas()
490    }
491
492    fn priority_fee_or_price(&self) -> u128 {
493        self.inner.priority_fee_or_price()
494    }
495
496    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
497        self.inner.effective_gas_price(base_fee)
498    }
499
500    fn is_dynamic_fee(&self) -> bool {
501        self.inner.is_dynamic_fee()
502    }
503
504    fn kind(&self) -> TxKind {
505        self.inner.kind()
506    }
507
508    fn is_create(&self) -> bool {
509        self.inner.is_create()
510    }
511
512    fn value(&self) -> U256 {
513        self.inner.value()
514    }
515
516    fn input(&self) -> &Bytes {
517        self.inner.input()
518    }
519
520    fn access_list(&self) -> Option<&AccessList> {
521        self.inner.access_list()
522    }
523
524    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
525        self.inner.blob_versioned_hashes()
526    }
527
528    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
529        self.inner.authorization_list()
530    }
531}
532
533impl TransactionResponse for AnyRpcTransaction {
534    fn tx_hash(&self) -> alloy_primitives::TxHash {
535        self.inner.tx_hash()
536    }
537
538    fn block_hash(&self) -> Option<alloy_primitives::BlockHash> {
539        self.0.inner.block_hash
540    }
541
542    fn block_number(&self) -> Option<u64> {
543        self.inner.block_number
544    }
545
546    fn transaction_index(&self) -> Option<u64> {
547        self.inner.transaction_index
548    }
549
550    fn from(&self) -> alloy_primitives::Address {
551        self.inner.from()
552    }
553
554    fn gas_price(&self) -> Option<u128> {
555        self.inner.effective_gas_price
556    }
557}
558
559impl Typed2718 for AnyRpcTransaction {
560    fn ty(&self) -> u8 {
561        self.inner.ty()
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568    use alloy_primitives::B64;
569
570    #[test]
571    fn convert_any_block() {
572        let block = AnyRpcBlock::new(
573            Block::new(
574                AnyRpcHeader::from_sealed(
575                    AnyHeader {
576                        nonce: Some(B64::ZERO),
577                        mix_hash: Some(B256::ZERO),
578                        ..Default::default()
579                    }
580                    .seal(B256::ZERO),
581                ),
582                BlockTransactions::Full(vec![]),
583            )
584            .into(),
585        );
586
587        let _block: alloy_consensus::Block<TxEnvelope, alloy_consensus::Header> =
588            block.try_into().unwrap();
589    }
590
591    #[test]
592    fn preserves_other_fields_when_converting_any_block_into_header() {
593        let mut block = AnyRpcBlock::new(
594            Block::new(
595                AnyRpcHeader::from_sealed(AnyHeader::default().seal(B256::ZERO)),
596                BlockTransactions::Full(vec![]),
597            )
598            .into(),
599        );
600        block.other.insert("timestampMillis".to_owned(), serde_json::json!(1_234_567));
601
602        let header = block.into_header_with_other();
603
604        assert_eq!(header.hash, B256::ZERO);
605        assert_eq!(header.other.get("timestampMillis"), Some(&serde_json::json!(1_234_567)));
606    }
607
608    #[test]
609    fn preserves_other_fields_when_converting_to_transaction_request() {
610        let tx: AnyRpcTransaction = serde_json::from_value(serde_json::json!({
611            "blockHash": "0x8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e",
612            "blockNumber": "0xf4240",
613            "hash": "0xe9e91f1ee4b56c0df2e9f06c2b8c27c6076195a88a7b8537ba8313d80e6f124e",
614            "transactionIndex": "0x1",
615            "type": "0x0",
616            "nonce": "0x43eb",
617            "input": "0x",
618            "r": "0x3b08715b4403c792b8c7567edea634088bedcd7f60d9352b1f16c69830f3afd5",
619            "s": "0x10b9afb67d2ec8b956f0e1dbc07eb79152904f3a7bf789fc869db56320adfe09",
620            "chainId": "0x0",
621            "v": "0x1c",
622            "gas": "0xc350",
623            "from": "0x32be343b94f860124dc4fee278fdcbd38c102d88",
624            "to": "0xdf190dc7190dfba737d7777a163445b7fff16133",
625            "value": "0x6113a84987be800",
626            "gasPrice": "0xdf8475800",
627            "tempoFeePayer": "0x1234",
628        }))
629        .unwrap();
630
631        let req: WithOtherFields<TransactionRequest> = tx.into();
632
633        assert_eq!(
634            req.other.get("tempoFeePayer").and_then(serde_json::Value::as_str),
635            Some("0x1234")
636        );
637        assert_eq!(
638            req.inner.from,
639            Some("0x32be343b94f860124dc4fee278fdcbd38c102d88".parse().unwrap())
640        );
641    }
642
643    #[test]
644    fn preserves_unknown_fields_when_converting_to_transaction_request() {
645        let tx: AnyRpcTransaction = serde_json::from_value(serde_json::json!({
646            "blockHash": "0xef664d656f841b5ad6a2b527b963f1eb48b97d7889d742f6cbff6950388e24cd",
647            "blockNumber": "0x73a78fd",
648            "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
649            "gas": "0xc27a8",
650            "gasPrice": "0x521",
651            "hash": "0x0bf1845c5d7a82ec92365d5027f7310793d53004f3c86aa80965c67bf7e7dc80",
652            "input": "0x",
653            "nonce": "0x74060",
654            "to": "0x4200000000000000000000000000000000000007",
655            "transactionIndex": "0x1",
656            "type": "0x7e",
657            "value": "0x0",
658            "sourceHash": "0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3",
659            "mint": "0x0",
660            "tempoFeePayer": "0x1234",
661        }))
662        .unwrap();
663
664        let req: WithOtherFields<TransactionRequest> = tx.into();
665
666        assert_eq!(req.other.get("type").and_then(serde_json::Value::as_u64), Some(0x7e));
667        assert_eq!(
668            req.other.get("sourceHash").and_then(serde_json::Value::as_str),
669            Some("0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3")
670        );
671        assert_eq!(
672            req.other.get("tempoFeePayer").and_then(serde_json::Value::as_str),
673            Some("0x1234")
674        );
675        assert_eq!(
676            req.inner.from,
677            Some("0x36bde71c97b33cc4729cf772ae268934f7ab70b2".parse().unwrap())
678        );
679        assert!(!req.other.contains_key("from"));
680    }
681}