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 try_into_consensus<T, H>(
109 self,
110 ) -> Result<alloy_consensus::Block<T, H>, AnyConversionError>
111 where
112 T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
113 H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
114 {
115 self.into_inner()
116 .map_header(|h| h.into_consensus())
117 .try_convert_header()
118 .map_err(AnyConversionError::new)?
119 .try_convert_transactions()
120 .map_err(AnyConversionError::new)
121 .map(Block::into_consensus_block)
122 }
123
124 pub fn try_into_sealed<T, H>(
130 self,
131 ) -> Result<Sealed<alloy_consensus::Block<T, H>>, AnyConversionError>
132 where
133 T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
134 H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
135 {
136 let block_hash = self.header.hash;
137 let block = self.try_into_consensus()?;
138 Ok(Sealed::new_unchecked(block, block_hash))
139 }
140
141 pub fn try_into_transactions(
145 self,
146 ) -> Result<Vec<AnyRpcTransaction>, ValueError<BlockTransactions<AnyRpcTransaction>>> {
147 self.0.inner.try_into_transactions()
148 }
149
150 pub fn into_transactions_iter(self) -> impl Iterator<Item = AnyRpcTransaction> {
152 self.into_inner().transactions.into_transactions()
153 }
154}
155
156impl BlockResponse for AnyRpcBlock {
157 type Header = AnyRpcHeader;
158 type Transaction = AnyRpcTransaction;
159
160 fn header(&self) -> &Self::Header {
161 &self.0.inner.header
162 }
163
164 fn transactions(&self) -> &BlockTransactions<Self::Transaction> {
165 &self.0.inner.transactions
166 }
167
168 fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
169 &mut self.0.inner.transactions
170 }
171
172 fn other_fields(&self) -> Option<&alloy_serde::OtherFields> {
173 self.0.other_fields()
174 }
175}
176
177impl AsRef<WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>> for AnyRpcBlock {
178 fn as_ref(&self) -> &WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
179 &self.0
180 }
181}
182
183impl Deref for AnyRpcBlock {
184 type Target = WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>;
185
186 fn deref(&self) -> &Self::Target {
187 &self.0
188 }
189}
190
191impl DerefMut for AnyRpcBlock {
192 fn deref_mut(&mut self) -> &mut Self::Target {
193 &mut self.0
194 }
195}
196
197impl From<Block> for AnyRpcBlock {
198 fn from(value: Block) -> Self {
199 let block = value.map_header(|h| h.map(|h| h.into())).map_transactions(|tx| {
200 AnyRpcTransaction::new(WithOtherFields::new(tx.map(AnyTxEnvelope::Ethereum)))
201 });
202
203 Self(WithOtherFields::new(block))
204 }
205}
206
207impl From<AnyRpcBlock> for Block<AnyRpcTransaction, AnyRpcHeader> {
208 fn from(value: AnyRpcBlock) -> Self {
209 value.into_inner()
210 }
211}
212impl From<AnyRpcBlock> for WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
213 fn from(value: AnyRpcBlock) -> Self {
214 value.0
215 }
216}
217
218impl<T, H> TryFrom<AnyRpcBlock> for alloy_consensus::Block<T, H>
219where
220 T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
221 H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
222{
223 type Error = AnyConversionError;
224
225 fn try_from(value: AnyRpcBlock) -> Result<Self, Self::Error> {
226 value.try_into_consensus()
227 }
228}
229
230#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
232pub struct AnyRpcTransaction(pub WithOtherFields<Transaction<AnyTxEnvelope>>);
233
234impl AnyRpcTransaction {
235 pub const fn new(inner: WithOtherFields<Transaction<AnyTxEnvelope>>) -> Self {
237 Self(inner)
238 }
239
240 pub fn into_parts(self) -> (Transaction<AnyTxEnvelope>, alloy_serde::OtherFields) {
242 let WithOtherFields { inner, other } = self.0;
243 (inner, other)
244 }
245
246 pub fn into_inner(self) -> Transaction<AnyTxEnvelope> {
248 self.0.into_inner()
249 }
250
251 pub fn as_envelope(&self) -> Option<&TxEnvelope> {
254 self.inner.inner.as_envelope()
255 }
256
257 pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<AnyTxEnvelope>> {
260 self.0.inner.inner.into_inner().try_into_envelope()
261 }
262
263 pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
265 self.0.inner().inner.as_legacy()
266 }
267
268 pub fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
270 self.0.inner().inner.as_eip2930()
271 }
272
273 pub fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
275 self.0.inner().inner.as_eip1559()
276 }
277
278 pub fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
280 self.0.inner().inner.as_eip4844()
281 }
282
283 pub fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
285 self.0.inner().inner.as_eip7702()
286 }
287
288 #[inline]
290 pub fn is_legacy(&self) -> bool {
291 self.0.inner().inner.is_legacy()
292 }
293
294 #[inline]
296 pub fn is_eip2930(&self) -> bool {
297 self.0.inner().inner.is_eip2930()
298 }
299
300 #[inline]
302 pub fn is_eip1559(&self) -> bool {
303 self.0.inner().inner.is_eip1559()
304 }
305
306 #[inline]
308 pub fn is_eip4844(&self) -> bool {
309 self.0.inner().inner.is_eip4844()
310 }
311
312 #[inline]
314 pub fn is_eip7702(&self) -> bool {
315 self.0.inner().inner.is_eip7702()
316 }
317
318 pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
324 where
325 T: TryFrom<Self>,
326 {
327 if self.0.inner.inner.inner().is_ethereum() {
328 Ok(Either::Left(self.0.inner.inner.into_inner().try_into_envelope().unwrap()))
329 } else {
330 T::try_from(self).map(Either::Right)
331 }
332 }
333
334 pub fn try_unknown_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
340 where
341 T: TryFrom<UnknownTxEnvelope>,
342 {
343 self.0.inner.inner.into_inner().try_into_either()
344 }
345
346 pub fn map<Tx>(self, f: impl FnOnce(AnyTxEnvelope) -> Tx) -> Transaction<Tx> {
351 self.into_inner().map(f)
352 }
353
354 pub fn try_map<Tx, E>(
358 self,
359 f: impl FnOnce(AnyTxEnvelope) -> Result<Tx, E>,
360 ) -> Result<Transaction<Tx>, E> {
361 self.into_inner().try_map(f)
362 }
363
364 pub fn convert<U>(self) -> Transaction<U>
368 where
369 U: From<AnyTxEnvelope>,
370 {
371 self.map(U::from)
372 }
373
374 pub fn try_convert<U>(self) -> Result<Transaction<U>, U::Error>
380 where
381 U: TryFrom<AnyTxEnvelope>,
382 {
383 self.try_map(U::try_from)
384 }
385}
386
387impl AsRef<AnyTxEnvelope> for AnyRpcTransaction {
388 fn as_ref(&self) -> &AnyTxEnvelope {
389 &self.0.inner.inner
390 }
391}
392
393impl Deref for AnyRpcTransaction {
394 type Target = WithOtherFields<Transaction<AnyTxEnvelope>>;
395
396 fn deref(&self) -> &Self::Target {
397 &self.0
398 }
399}
400
401impl DerefMut for AnyRpcTransaction {
402 fn deref_mut(&mut self) -> &mut Self::Target {
403 &mut self.0
404 }
405}
406
407impl From<Transaction<TxEnvelope>> for AnyRpcTransaction {
408 fn from(tx: Transaction<TxEnvelope>) -> Self {
409 let tx = tx.map(AnyTxEnvelope::Ethereum);
410 Self(WithOtherFields::new(tx))
411 }
412}
413
414impl From<AnyRpcTransaction> for AnyTxEnvelope {
415 fn from(tx: AnyRpcTransaction) -> Self {
416 tx.0.inner.into_inner()
417 }
418}
419
420impl From<AnyRpcTransaction> for Transaction<AnyTxEnvelope> {
421 fn from(tx: AnyRpcTransaction) -> Self {
422 tx.0.inner
423 }
424}
425
426impl From<AnyRpcTransaction> for WithOtherFields<Transaction<AnyTxEnvelope>> {
427 fn from(tx: AnyRpcTransaction) -> Self {
428 tx.0
429 }
430}
431
432impl From<AnyRpcTransaction> for Recovered<AnyTxEnvelope> {
433 fn from(tx: AnyRpcTransaction) -> Self {
434 tx.0.inner.inner
435 }
436}
437
438impl From<AnyRpcTransaction> for WithOtherFields<TransactionRequest> {
439 fn from(tx: AnyRpcTransaction) -> Self {
440 let (inner, other) = tx.into_parts();
441 let (envelope, from) = inner.into_recovered().into_parts();
442 let mut req: Self = envelope.into();
443 req.inner.from = Some(from);
444 req.other.extend(other);
445 req
446 }
447}
448
449impl TryFrom<AnyRpcTransaction> for TxEnvelope {
450 type Error = ValueError<AnyTxEnvelope>;
451
452 fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
453 value.try_into_envelope()
454 }
455}
456
457impl alloy_consensus::Transaction for AnyRpcTransaction {
458 fn chain_id(&self) -> Option<ChainId> {
459 self.inner.chain_id()
460 }
461
462 fn nonce(&self) -> u64 {
463 self.inner.nonce()
464 }
465
466 fn gas_limit(&self) -> u64 {
467 self.inner.gas_limit()
468 }
469
470 fn gas_price(&self) -> Option<u128> {
471 alloy_consensus::Transaction::gas_price(&self.0.inner)
472 }
473
474 fn max_fee_per_gas(&self) -> u128 {
475 alloy_consensus::Transaction::max_fee_per_gas(&self.inner)
476 }
477
478 fn max_priority_fee_per_gas(&self) -> Option<u128> {
479 self.inner.max_priority_fee_per_gas()
480 }
481
482 fn max_fee_per_blob_gas(&self) -> Option<u128> {
483 self.inner.max_fee_per_blob_gas()
484 }
485
486 fn priority_fee_or_price(&self) -> u128 {
487 self.inner.priority_fee_or_price()
488 }
489
490 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
491 self.inner.effective_gas_price(base_fee)
492 }
493
494 fn is_dynamic_fee(&self) -> bool {
495 self.inner.is_dynamic_fee()
496 }
497
498 fn kind(&self) -> TxKind {
499 self.inner.kind()
500 }
501
502 fn is_create(&self) -> bool {
503 self.inner.is_create()
504 }
505
506 fn value(&self) -> U256 {
507 self.inner.value()
508 }
509
510 fn input(&self) -> &Bytes {
511 self.inner.input()
512 }
513
514 fn access_list(&self) -> Option<&AccessList> {
515 self.inner.access_list()
516 }
517
518 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
519 self.inner.blob_versioned_hashes()
520 }
521
522 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
523 self.inner.authorization_list()
524 }
525}
526
527impl TransactionResponse for AnyRpcTransaction {
528 fn tx_hash(&self) -> alloy_primitives::TxHash {
529 self.inner.tx_hash()
530 }
531
532 fn block_hash(&self) -> Option<alloy_primitives::BlockHash> {
533 self.0.inner.block_hash
534 }
535
536 fn block_number(&self) -> Option<u64> {
537 self.inner.block_number
538 }
539
540 fn transaction_index(&self) -> Option<u64> {
541 self.inner.transaction_index
542 }
543
544 fn from(&self) -> alloy_primitives::Address {
545 self.inner.from()
546 }
547
548 fn gas_price(&self) -> Option<u128> {
549 self.inner.effective_gas_price
550 }
551}
552
553impl Typed2718 for AnyRpcTransaction {
554 fn ty(&self) -> u8 {
555 self.inner.ty()
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562 use alloy_primitives::B64;
563
564 #[test]
565 fn convert_any_block() {
566 let block = AnyRpcBlock::new(
567 Block::new(
568 AnyRpcHeader::from_sealed(
569 AnyHeader {
570 nonce: Some(B64::ZERO),
571 mix_hash: Some(B256::ZERO),
572 ..Default::default()
573 }
574 .seal(B256::ZERO),
575 ),
576 BlockTransactions::Full(vec![]),
577 )
578 .into(),
579 );
580
581 let _block: alloy_consensus::Block<TxEnvelope, alloy_consensus::Header> =
582 block.try_into().unwrap();
583 }
584
585 #[test]
586 fn preserves_other_fields_when_converting_to_transaction_request() {
587 let tx: AnyRpcTransaction = serde_json::from_value(serde_json::json!({
588 "blockHash": "0x8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e",
589 "blockNumber": "0xf4240",
590 "hash": "0xe9e91f1ee4b56c0df2e9f06c2b8c27c6076195a88a7b8537ba8313d80e6f124e",
591 "transactionIndex": "0x1",
592 "type": "0x0",
593 "nonce": "0x43eb",
594 "input": "0x",
595 "r": "0x3b08715b4403c792b8c7567edea634088bedcd7f60d9352b1f16c69830f3afd5",
596 "s": "0x10b9afb67d2ec8b956f0e1dbc07eb79152904f3a7bf789fc869db56320adfe09",
597 "chainId": "0x0",
598 "v": "0x1c",
599 "gas": "0xc350",
600 "from": "0x32be343b94f860124dc4fee278fdcbd38c102d88",
601 "to": "0xdf190dc7190dfba737d7777a163445b7fff16133",
602 "value": "0x6113a84987be800",
603 "gasPrice": "0xdf8475800",
604 "tempoFeePayer": "0x1234",
605 }))
606 .unwrap();
607
608 let req: WithOtherFields<TransactionRequest> = tx.into();
609
610 assert_eq!(
611 req.other.get("tempoFeePayer").and_then(serde_json::Value::as_str),
612 Some("0x1234")
613 );
614 assert_eq!(
615 req.inner.from,
616 Some("0x32be343b94f860124dc4fee278fdcbd38c102d88".parse().unwrap())
617 );
618 }
619
620 #[test]
621 fn preserves_unknown_fields_when_converting_to_transaction_request() {
622 let tx: AnyRpcTransaction = serde_json::from_value(serde_json::json!({
623 "blockHash": "0xef664d656f841b5ad6a2b527b963f1eb48b97d7889d742f6cbff6950388e24cd",
624 "blockNumber": "0x73a78fd",
625 "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
626 "gas": "0xc27a8",
627 "gasPrice": "0x521",
628 "hash": "0x0bf1845c5d7a82ec92365d5027f7310793d53004f3c86aa80965c67bf7e7dc80",
629 "input": "0x",
630 "nonce": "0x74060",
631 "to": "0x4200000000000000000000000000000000000007",
632 "transactionIndex": "0x1",
633 "type": "0x7e",
634 "value": "0x0",
635 "sourceHash": "0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3",
636 "mint": "0x0",
637 "tempoFeePayer": "0x1234",
638 }))
639 .unwrap();
640
641 let req: WithOtherFields<TransactionRequest> = tx.into();
642
643 assert_eq!(req.other.get("type").and_then(serde_json::Value::as_u64), Some(0x7e));
644 assert_eq!(
645 req.other.get("sourceHash").and_then(serde_json::Value::as_str),
646 Some("0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3")
647 );
648 assert_eq!(
649 req.other.get("tempoFeePayer").and_then(serde_json::Value::as_str),
650 Some("0x1234")
651 );
652 assert_eq!(
653 req.inner.from,
654 Some("0x36bde71c97b33cc4729cf772ae268934f7ab70b2".parse().unwrap())
655 );
656 assert!(!req.other.contains_key("from"));
657 }
658}