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#[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#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
92pub struct AnyRpcBlock(pub WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>);
93
94impl AnyRpcBlock {
95 pub const fn new(inner: WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>) -> Self {
97 Self(inner)
98 }
99
100 pub fn into_inner(self) -> Block<AnyRpcTransaction, AnyRpcHeader> {
102 self.0.into_inner()
103 }
104
105 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 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 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 pub fn try_into_transactions(
151 self,
152 ) -> Result<Vec<AnyRpcTransaction>, ValueError<BlockTransactions<AnyRpcTransaction>>> {
153 self.0.inner.try_into_transactions()
154 }
155
156 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#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
238pub struct AnyRpcTransaction(pub WithOtherFields<Transaction<AnyTxEnvelope>>);
239
240impl AnyRpcTransaction {
241 pub const fn new(inner: WithOtherFields<Transaction<AnyTxEnvelope>>) -> Self {
243 Self(inner)
244 }
245
246 pub fn into_parts(self) -> (Transaction<AnyTxEnvelope>, alloy_serde::OtherFields) {
248 let WithOtherFields { inner, other } = self.0;
249 (inner, other)
250 }
251
252 pub fn into_inner(self) -> Transaction<AnyTxEnvelope> {
254 self.0.into_inner()
255 }
256
257 pub fn as_envelope(&self) -> Option<&TxEnvelope> {
260 self.inner.inner.as_envelope()
261 }
262
263 pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<AnyTxEnvelope>> {
266 self.0.inner.inner.into_inner().try_into_envelope()
267 }
268
269 pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
271 self.0.inner().inner.as_legacy()
272 }
273
274 pub fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
276 self.0.inner().inner.as_eip2930()
277 }
278
279 pub fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
281 self.0.inner().inner.as_eip1559()
282 }
283
284 pub fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
286 self.0.inner().inner.as_eip4844()
287 }
288
289 pub fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
291 self.0.inner().inner.as_eip7702()
292 }
293
294 #[inline]
296 pub fn is_legacy(&self) -> bool {
297 self.0.inner().inner.is_legacy()
298 }
299
300 #[inline]
302 pub fn is_eip2930(&self) -> bool {
303 self.0.inner().inner.is_eip2930()
304 }
305
306 #[inline]
308 pub fn is_eip1559(&self) -> bool {
309 self.0.inner().inner.is_eip1559()
310 }
311
312 #[inline]
314 pub fn is_eip4844(&self) -> bool {
315 self.0.inner().inner.is_eip4844()
316 }
317
318 #[inline]
320 pub fn is_eip7702(&self) -> bool {
321 self.0.inner().inner.is_eip7702()
322 }
323
324 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 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 pub fn map<Tx>(self, f: impl FnOnce(AnyTxEnvelope) -> Tx) -> Transaction<Tx> {
357 self.into_inner().map(f)
358 }
359
360 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 pub fn convert<U>(self) -> Transaction<U>
374 where
375 U: From<AnyTxEnvelope>,
376 {
377 self.map(U::from)
378 }
379
380 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}