1use alloc::vec::Vec;
4use alloy_consensus::{
5 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
6 Sealed, SignableTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxEip7702, TxLegacy,
7 Typed2718,
8};
9use alloy_eips::{
10 eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
11 eip2930::AccessList,
12 eip7702::SignedAuthorization,
13};
14use alloy_evm::FromRecoveredTx;
15use alloy_primitives::{
16 keccak256, Address, Bytes, PrimitiveSignature as Signature, TxHash, TxKind, Uint, B256,
17};
18use alloy_rlp::Header;
19use core::{
20 hash::{Hash, Hasher},
21 mem,
22 ops::Deref,
23};
24use derive_more::{AsRef, Deref};
25use op_alloy_consensus::{
26 DepositTransaction, OpPooledTransaction, OpTxEnvelope, OpTypedTransaction, TxDeposit,
27};
28use op_revm::transaction::deposit::DepositTransactionParts;
29#[cfg(any(test, feature = "reth-codec"))]
30use proptest as _;
31use reth_primitives_traits::{
32 crypto::secp256k1::{recover_signer, recover_signer_unchecked},
33 sync::OnceLock,
34 transaction::{error::TransactionConversionError, signed::RecoveryError},
35 InMemorySize, SignedTransaction,
36};
37use revm_context::TxEnv;
38
39#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42#[derive(Debug, Clone, Eq, AsRef, Deref)]
43pub struct OpTransactionSigned {
44 #[cfg_attr(feature = "serde", serde(skip))]
46 hash: OnceLock<TxHash>,
47 signature: Signature,
49 #[deref]
51 #[as_ref]
52 transaction: OpTypedTransaction,
53}
54
55impl OpTransactionSigned {
56 pub fn new(transaction: OpTypedTransaction, signature: Signature, hash: B256) -> Self {
58 Self { hash: hash.into(), signature, transaction }
59 }
60
61 #[inline]
63 pub fn into_transaction(self) -> OpTypedTransaction {
64 self.transaction
65 }
66
67 #[inline]
69 pub const fn transaction(&self) -> &OpTypedTransaction {
70 &self.transaction
71 }
72
73 pub fn split(self) -> (OpTypedTransaction, Signature) {
75 (self.transaction, self.signature)
76 }
77
78 pub fn new_unhashed(transaction: OpTypedTransaction, signature: Signature) -> Self {
82 Self { hash: Default::default(), signature, transaction }
83 }
84
85 pub const fn is_deposit(&self) -> bool {
87 matches!(self.transaction, OpTypedTransaction::Deposit(_))
88 }
89
90 pub fn into_parts(self) -> (OpTypedTransaction, Signature, B256) {
92 let hash = *self.hash.get_or_init(|| self.recalculate_hash());
93 (self.transaction, self.signature, hash)
94 }
95}
96
97impl SignedTransaction for OpTransactionSigned {
98 fn tx_hash(&self) -> &TxHash {
99 self.hash.get_or_init(|| self.recalculate_hash())
100 }
101
102 fn signature(&self) -> &Signature {
103 &self.signature
104 }
105
106 fn recover_signer(&self) -> Result<Address, RecoveryError> {
107 if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = self.transaction {
110 return Ok(from)
111 }
112
113 let Self { transaction, signature, .. } = self;
114 let signature_hash = signature_hash(transaction);
115 recover_signer(signature, signature_hash)
116 }
117
118 fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
119 if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = &self.transaction {
122 return Ok(*from)
123 }
124
125 let Self { transaction, signature, .. } = self;
126 let signature_hash = signature_hash(transaction);
127 recover_signer_unchecked(signature, signature_hash)
128 }
129
130 fn recover_signer_unchecked_with_buf(
131 &self,
132 buf: &mut Vec<u8>,
133 ) -> Result<Address, RecoveryError> {
134 match &self.transaction {
135 OpTypedTransaction::Deposit(tx) => return Ok(tx.from),
138 OpTypedTransaction::Legacy(tx) => tx.encode_for_signing(buf),
139 OpTypedTransaction::Eip2930(tx) => tx.encode_for_signing(buf),
140 OpTypedTransaction::Eip1559(tx) => tx.encode_for_signing(buf),
141 OpTypedTransaction::Eip7702(tx) => tx.encode_for_signing(buf),
142 };
143 recover_signer_unchecked(&self.signature, keccak256(buf))
144 }
145
146 fn recalculate_hash(&self) -> B256 {
147 keccak256(self.encoded_2718())
148 }
149}
150
151macro_rules! impl_from_signed {
152 ($($tx:ident),*) => {
153 $(
154 impl From<Signed<$tx>> for OpTransactionSigned {
155 fn from(value: Signed<$tx>) -> Self {
156 let(tx,sig,hash) = value.into_parts();
157 Self::new(tx.into(), sig, hash)
158 }
159 }
160 )*
161 };
162}
163
164impl_from_signed!(TxLegacy, TxEip2930, TxEip1559, TxEip7702, OpTypedTransaction);
165
166impl From<OpTxEnvelope> for OpTransactionSigned {
167 fn from(value: OpTxEnvelope) -> Self {
168 match value {
169 OpTxEnvelope::Legacy(tx) => tx.into(),
170 OpTxEnvelope::Eip2930(tx) => tx.into(),
171 OpTxEnvelope::Eip1559(tx) => tx.into(),
172 OpTxEnvelope::Eip7702(tx) => tx.into(),
173 OpTxEnvelope::Deposit(tx) => tx.into(),
174 }
175 }
176}
177
178impl From<OpTransactionSigned> for OpTxEnvelope {
179 fn from(value: OpTransactionSigned) -> Self {
180 let (tx, signature, hash) = value.into_parts();
181 match tx {
182 OpTypedTransaction::Legacy(tx) => Signed::new_unchecked(tx, signature, hash).into(),
183 OpTypedTransaction::Eip2930(tx) => Signed::new_unchecked(tx, signature, hash).into(),
184 OpTypedTransaction::Eip1559(tx) => Signed::new_unchecked(tx, signature, hash).into(),
185 OpTypedTransaction::Deposit(tx) => Sealed::new_unchecked(tx, hash).into(),
186 OpTypedTransaction::Eip7702(tx) => Signed::new_unchecked(tx, signature, hash).into(),
187 }
188 }
189}
190
191impl From<OpTransactionSigned> for Signed<OpTypedTransaction> {
192 fn from(value: OpTransactionSigned) -> Self {
193 let (tx, sig, hash) = value.into_parts();
194 Self::new_unchecked(tx, sig, hash)
195 }
196}
197
198impl From<Sealed<TxDeposit>> for OpTransactionSigned {
199 fn from(value: Sealed<TxDeposit>) -> Self {
200 let (tx, hash) = value.into_parts();
201 Self::new(OpTypedTransaction::Deposit(tx), TxDeposit::signature(), hash)
202 }
203}
204
205pub trait OpTransaction {
208 fn is_deposit(&self) -> bool;
210}
211
212impl OpTransaction for OpTransactionSigned {
213 fn is_deposit(&self) -> bool {
214 self.is_deposit()
215 }
216}
217
218impl FromRecoveredTx<OpTransactionSigned> for op_revm::OpTransaction<TxEnv> {
219 fn from_recovered_tx(tx: &OpTransactionSigned, sender: Address) -> Self {
220 let envelope = tx.encoded_2718();
221
222 let base = match &tx.transaction {
223 OpTypedTransaction::Legacy(tx) => TxEnv {
224 gas_limit: tx.gas_limit,
225 gas_price: tx.gas_price,
226 gas_priority_fee: None,
227 kind: tx.to,
228 value: tx.value,
229 data: tx.input.clone(),
230 chain_id: tx.chain_id,
231 nonce: tx.nonce,
232 access_list: Default::default(),
233 blob_hashes: Default::default(),
234 max_fee_per_blob_gas: Default::default(),
235 authorization_list: Default::default(),
236 tx_type: 0,
237 caller: sender,
238 },
239 OpTypedTransaction::Eip2930(tx) => TxEnv {
240 gas_limit: tx.gas_limit,
241 gas_price: tx.gas_price,
242 gas_priority_fee: None,
243 kind: tx.to,
244 value: tx.value,
245 data: tx.input.clone(),
246 chain_id: Some(tx.chain_id),
247 nonce: tx.nonce,
248 access_list: tx.access_list.clone(),
249 blob_hashes: Default::default(),
250 max_fee_per_blob_gas: Default::default(),
251 authorization_list: Default::default(),
252 tx_type: 1,
253 caller: sender,
254 },
255 OpTypedTransaction::Eip1559(tx) => TxEnv {
256 gas_limit: tx.gas_limit,
257 gas_price: tx.max_fee_per_gas,
258 gas_priority_fee: Some(tx.max_priority_fee_per_gas),
259 kind: tx.to,
260 value: tx.value,
261 data: tx.input.clone(),
262 chain_id: Some(tx.chain_id),
263 nonce: tx.nonce,
264 access_list: tx.access_list.clone(),
265 blob_hashes: Default::default(),
266 max_fee_per_blob_gas: Default::default(),
267 authorization_list: Default::default(),
268 tx_type: 2,
269 caller: sender,
270 },
271 OpTypedTransaction::Eip7702(tx) => TxEnv {
272 gas_limit: tx.gas_limit,
273 gas_price: tx.max_fee_per_gas,
274 gas_priority_fee: Some(tx.max_priority_fee_per_gas),
275 kind: TxKind::Call(tx.to),
276 value: tx.value,
277 data: tx.input.clone(),
278 chain_id: Some(tx.chain_id),
279 nonce: tx.nonce,
280 access_list: tx.access_list.clone(),
281 blob_hashes: Default::default(),
282 max_fee_per_blob_gas: Default::default(),
283 authorization_list: tx.authorization_list.clone(),
284 tx_type: 4,
285 caller: sender,
286 },
287 OpTypedTransaction::Deposit(tx) => TxEnv {
288 gas_limit: tx.gas_limit,
289 gas_price: 0,
290 kind: tx.to,
291 value: tx.value,
292 data: tx.input.clone(),
293 chain_id: None,
294 nonce: 0,
295 access_list: Default::default(),
296 blob_hashes: Default::default(),
297 max_fee_per_blob_gas: Default::default(),
298 authorization_list: Default::default(),
299 gas_priority_fee: Default::default(),
300 tx_type: 126,
301 caller: sender,
302 },
303 };
304
305 Self {
306 base,
307 enveloped_tx: Some(envelope.into()),
308 deposit: if let OpTypedTransaction::Deposit(tx) = &tx.transaction {
309 DepositTransactionParts {
310 is_system_transaction: tx.is_system_transaction,
311 source_hash: tx.source_hash,
312 mint: Some(tx.mint.unwrap_or_default()),
317 }
318 } else {
319 Default::default()
320 },
321 }
322 }
323}
324
325impl InMemorySize for OpTransactionSigned {
326 #[inline]
327 fn size(&self) -> usize {
328 mem::size_of::<TxHash>() + self.transaction.size() + mem::size_of::<Signature>()
329 }
330}
331
332impl alloy_rlp::Encodable for OpTransactionSigned {
333 fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
334 self.network_encode(out);
335 }
336
337 fn length(&self) -> usize {
338 let mut payload_length = self.encode_2718_len();
339 if !self.is_legacy() {
340 payload_length += Header { list: false, payload_length }.length();
341 }
342
343 payload_length
344 }
345}
346
347impl alloy_rlp::Decodable for OpTransactionSigned {
348 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
349 Self::network_decode(buf).map_err(Into::into)
350 }
351}
352
353impl Encodable2718 for OpTransactionSigned {
354 fn type_flag(&self) -> Option<u8> {
355 if Typed2718::is_legacy(self) {
356 None
357 } else {
358 Some(self.ty())
359 }
360 }
361
362 fn encode_2718_len(&self) -> usize {
363 match &self.transaction {
364 OpTypedTransaction::Legacy(legacy_tx) => {
365 legacy_tx.eip2718_encoded_length(&self.signature)
366 }
367 OpTypedTransaction::Eip2930(access_list_tx) => {
368 access_list_tx.eip2718_encoded_length(&self.signature)
369 }
370 OpTypedTransaction::Eip1559(dynamic_fee_tx) => {
371 dynamic_fee_tx.eip2718_encoded_length(&self.signature)
372 }
373 OpTypedTransaction::Eip7702(set_code_tx) => {
374 set_code_tx.eip2718_encoded_length(&self.signature)
375 }
376 OpTypedTransaction::Deposit(deposit_tx) => deposit_tx.eip2718_encoded_length(),
377 }
378 }
379
380 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
381 let Self { transaction, signature, .. } = self;
382
383 match &transaction {
384 OpTypedTransaction::Legacy(legacy_tx) => {
385 legacy_tx.eip2718_encode(signature, out)
387 }
388 OpTypedTransaction::Eip2930(access_list_tx) => {
389 access_list_tx.eip2718_encode(signature, out)
390 }
391 OpTypedTransaction::Eip1559(dynamic_fee_tx) => {
392 dynamic_fee_tx.eip2718_encode(signature, out)
393 }
394 OpTypedTransaction::Eip7702(set_code_tx) => set_code_tx.eip2718_encode(signature, out),
395 OpTypedTransaction::Deposit(deposit_tx) => deposit_tx.encode_2718(out),
396 }
397 }
398}
399
400impl Decodable2718 for OpTransactionSigned {
401 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
402 match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
403 op_alloy_consensus::OpTxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
404 op_alloy_consensus::OpTxType::Eip2930 => {
405 let (tx, signature, hash) = TxEip2930::rlp_decode_signed(buf)?.into_parts();
406 let signed_tx = Self::new_unhashed(OpTypedTransaction::Eip2930(tx), signature);
407 signed_tx.hash.get_or_init(|| hash);
408 Ok(signed_tx)
409 }
410 op_alloy_consensus::OpTxType::Eip1559 => {
411 let (tx, signature, hash) = TxEip1559::rlp_decode_signed(buf)?.into_parts();
412 let signed_tx = Self::new_unhashed(OpTypedTransaction::Eip1559(tx), signature);
413 signed_tx.hash.get_or_init(|| hash);
414 Ok(signed_tx)
415 }
416 op_alloy_consensus::OpTxType::Eip7702 => {
417 let (tx, signature, hash) = TxEip7702::rlp_decode_signed(buf)?.into_parts();
418 let signed_tx = Self::new_unhashed(OpTypedTransaction::Eip7702(tx), signature);
419 signed_tx.hash.get_or_init(|| hash);
420 Ok(signed_tx)
421 }
422 op_alloy_consensus::OpTxType::Deposit => Ok(Self::new_unhashed(
423 OpTypedTransaction::Deposit(TxDeposit::rlp_decode(buf)?),
424 TxDeposit::signature(),
425 )),
426 }
427 }
428
429 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
430 let (transaction, signature) = TxLegacy::rlp_decode_with_signature(buf)?;
431 let signed_tx = Self::new_unhashed(OpTypedTransaction::Legacy(transaction), signature);
432
433 Ok(signed_tx)
434 }
435}
436
437impl Transaction for OpTransactionSigned {
438 fn chain_id(&self) -> Option<u64> {
439 self.deref().chain_id()
440 }
441
442 fn nonce(&self) -> u64 {
443 self.deref().nonce()
444 }
445
446 fn gas_limit(&self) -> u64 {
447 self.deref().gas_limit()
448 }
449
450 fn gas_price(&self) -> Option<u128> {
451 self.deref().gas_price()
452 }
453
454 fn max_fee_per_gas(&self) -> u128 {
455 self.deref().max_fee_per_gas()
456 }
457
458 fn max_priority_fee_per_gas(&self) -> Option<u128> {
459 self.deref().max_priority_fee_per_gas()
460 }
461
462 fn max_fee_per_blob_gas(&self) -> Option<u128> {
463 self.deref().max_fee_per_blob_gas()
464 }
465
466 fn priority_fee_or_price(&self) -> u128 {
467 self.deref().priority_fee_or_price()
468 }
469
470 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
471 self.deref().effective_gas_price(base_fee)
472 }
473
474 fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
475 self.deref().effective_tip_per_gas(base_fee)
476 }
477
478 fn is_dynamic_fee(&self) -> bool {
479 self.deref().is_dynamic_fee()
480 }
481
482 fn kind(&self) -> TxKind {
483 self.deref().kind()
484 }
485
486 fn is_create(&self) -> bool {
487 self.deref().is_create()
488 }
489
490 fn value(&self) -> Uint<256, 4> {
491 self.deref().value()
492 }
493
494 fn input(&self) -> &Bytes {
495 self.deref().input()
496 }
497
498 fn access_list(&self) -> Option<&AccessList> {
499 self.deref().access_list()
500 }
501
502 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
503 self.deref().blob_versioned_hashes()
504 }
505
506 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
507 self.deref().authorization_list()
508 }
509}
510
511impl Typed2718 for OpTransactionSigned {
512 fn ty(&self) -> u8 {
513 self.deref().ty()
514 }
515}
516
517impl PartialEq for OpTransactionSigned {
518 fn eq(&self, other: &Self) -> bool {
519 self.signature == other.signature &&
520 self.transaction == other.transaction &&
521 self.tx_hash() == other.tx_hash()
522 }
523}
524
525impl Hash for OpTransactionSigned {
526 fn hash<H: Hasher>(&self, state: &mut H) {
527 self.signature.hash(state);
528 self.transaction.hash(state);
529 }
530}
531
532#[cfg(feature = "reth-codec")]
533impl reth_codecs::Compact for OpTransactionSigned {
534 fn to_compact<B>(&self, buf: &mut B) -> usize
535 where
536 B: bytes::BufMut + AsMut<[u8]>,
537 {
538 let start = buf.as_mut().len();
539
540 buf.put_u8(0);
543
544 let sig_bit = self.signature.to_compact(buf) as u8;
545 let zstd_bit = self.transaction.input().len() >= 32;
546
547 let tx_bits = if zstd_bit {
548 let mut tmp = Vec::with_capacity(256);
549 if cfg!(feature = "std") {
550 reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| {
551 let mut compressor = compressor.borrow_mut();
552 let tx_bits = self.transaction.to_compact(&mut tmp);
553 buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
554 tx_bits as u8
555 })
556 } else {
557 let mut compressor = reth_zstd_compressors::create_tx_compressor();
558 let tx_bits = self.transaction.to_compact(&mut tmp);
559 buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
560 tx_bits as u8
561 }
562 } else {
563 self.transaction.to_compact(buf) as u8
564 };
565
566 buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
568
569 buf.as_mut().len() - start
570 }
571
572 fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
573 use bytes::Buf;
574
575 let bitflags = buf.get_u8() as usize;
577
578 let sig_bit = bitflags & 1;
579 let (signature, buf) = Signature::from_compact(buf, sig_bit);
580
581 let zstd_bit = bitflags >> 3;
582 let (transaction, buf) = if zstd_bit != 0 {
583 if cfg!(feature = "std") {
584 reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| {
585 let mut decompressor = decompressor.borrow_mut();
586
587 let transaction_type = (bitflags & 0b110) >> 1;
589 let (transaction, _) = OpTypedTransaction::from_compact(
590 decompressor.decompress(buf),
591 transaction_type,
592 );
593
594 (transaction, buf)
595 })
596 } else {
597 let mut decompressor = reth_zstd_compressors::create_tx_decompressor();
598 let transaction_type = (bitflags & 0b110) >> 1;
599 let (transaction, _) = OpTypedTransaction::from_compact(
600 decompressor.decompress(buf),
601 transaction_type,
602 );
603
604 (transaction, buf)
605 }
606 } else {
607 let transaction_type = bitflags >> 1;
608 OpTypedTransaction::from_compact(buf, transaction_type)
609 };
610
611 (Self { signature, transaction, hash: Default::default() }, buf)
612 }
613}
614
615#[cfg(any(test, feature = "arbitrary"))]
616impl<'a> arbitrary::Arbitrary<'a> for OpTransactionSigned {
617 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
618 #[allow(unused_mut)]
619 let mut transaction = OpTypedTransaction::arbitrary(u)?;
620
621 let secp = secp256k1::Secp256k1::new();
622 let key_pair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng());
623 let signature = reth_primitives_traits::crypto::secp256k1::sign_message(
624 B256::from_slice(&key_pair.secret_bytes()[..]),
625 signature_hash(&transaction),
626 )
627 .unwrap();
628
629 if let OpTypedTransaction::Deposit(ref mut tx_deposit) = transaction {
633 if tx_deposit.mint == Some(0) {
634 tx_deposit.mint = None;
635 }
636 }
637
638 let signature = if is_deposit(&transaction) { TxDeposit::signature() } else { signature };
639
640 Ok(Self::new_unhashed(transaction, signature))
641 }
642}
643
644fn signature_hash(tx: &OpTypedTransaction) -> B256 {
646 match tx {
647 OpTypedTransaction::Legacy(tx) => tx.signature_hash(),
648 OpTypedTransaction::Eip2930(tx) => tx.signature_hash(),
649 OpTypedTransaction::Eip1559(tx) => tx.signature_hash(),
650 OpTypedTransaction::Eip7702(tx) => tx.signature_hash(),
651 OpTypedTransaction::Deposit(_) => B256::ZERO,
652 }
653}
654
655pub const fn is_deposit(tx: &OpTypedTransaction) -> bool {
657 matches!(tx, OpTypedTransaction::Deposit(_))
658}
659
660impl From<OpPooledTransaction> for OpTransactionSigned {
661 fn from(value: OpPooledTransaction) -> Self {
662 match value {
663 OpPooledTransaction::Legacy(tx) => tx.into(),
664 OpPooledTransaction::Eip2930(tx) => tx.into(),
665 OpPooledTransaction::Eip1559(tx) => tx.into(),
666 OpPooledTransaction::Eip7702(tx) => tx.into(),
667 }
668 }
669}
670
671impl TryFrom<OpTransactionSigned> for OpPooledTransaction {
672 type Error = TransactionConversionError;
673
674 fn try_from(value: OpTransactionSigned) -> Result<Self, Self::Error> {
675 let hash = *value.tx_hash();
676 let OpTransactionSigned { hash: _, signature, transaction } = value;
677
678 match transaction {
679 OpTypedTransaction::Legacy(tx) => {
680 Ok(Self::Legacy(Signed::new_unchecked(tx, signature, hash)))
681 }
682 OpTypedTransaction::Eip2930(tx) => {
683 Ok(Self::Eip2930(Signed::new_unchecked(tx, signature, hash)))
684 }
685 OpTypedTransaction::Eip1559(tx) => {
686 Ok(Self::Eip1559(Signed::new_unchecked(tx, signature, hash)))
687 }
688 OpTypedTransaction::Eip7702(tx) => {
689 Ok(Self::Eip7702(Signed::new_unchecked(tx, signature, hash)))
690 }
691 OpTypedTransaction::Deposit(_) => Err(TransactionConversionError::UnsupportedForP2P),
692 }
693 }
694}
695
696impl DepositTransaction for OpTransactionSigned {
697 fn source_hash(&self) -> Option<B256> {
698 match &self.transaction {
699 OpTypedTransaction::Deposit(tx) => Some(tx.source_hash),
700 _ => None,
701 }
702 }
703
704 fn mint(&self) -> Option<u128> {
705 match &self.transaction {
706 OpTypedTransaction::Deposit(tx) => tx.mint,
707 _ => None,
708 }
709 }
710
711 fn is_system_transaction(&self) -> bool {
712 self.is_deposit()
713 }
714}
715
716#[cfg(feature = "serde-bincode-compat")]
718pub mod serde_bincode_compat {
719 use alloy_consensus::transaction::serde_bincode_compat::{
720 TxEip1559, TxEip2930, TxEip7702, TxLegacy,
721 };
722 use alloy_primitives::{PrimitiveSignature as Signature, TxHash};
723 use reth_primitives_traits::{serde_bincode_compat::SerdeBincodeCompat, SignedTransaction};
724 use serde::{Deserialize, Serialize};
725
726 #[derive(Debug, Serialize, Deserialize)]
728 #[allow(missing_docs)]
729 enum OpTypedTransaction<'a> {
730 Legacy(TxLegacy<'a>),
731 Eip2930(TxEip2930<'a>),
732 Eip1559(TxEip1559<'a>),
733 Eip7702(TxEip7702<'a>),
734 Deposit(op_alloy_consensus::serde_bincode_compat::TxDeposit<'a>),
735 }
736
737 impl<'a> From<&'a super::OpTypedTransaction> for OpTypedTransaction<'a> {
738 fn from(value: &'a super::OpTypedTransaction) -> Self {
739 match value {
740 super::OpTypedTransaction::Legacy(tx) => Self::Legacy(TxLegacy::from(tx)),
741 super::OpTypedTransaction::Eip2930(tx) => Self::Eip2930(TxEip2930::from(tx)),
742 super::OpTypedTransaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)),
743 super::OpTypedTransaction::Eip7702(tx) => Self::Eip7702(TxEip7702::from(tx)),
744 super::OpTypedTransaction::Deposit(tx) => {
745 Self::Deposit(op_alloy_consensus::serde_bincode_compat::TxDeposit::from(tx))
746 }
747 }
748 }
749 }
750
751 impl<'a> From<OpTypedTransaction<'a>> for super::OpTypedTransaction {
752 fn from(value: OpTypedTransaction<'a>) -> Self {
753 match value {
754 OpTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
755 OpTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
756 OpTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
757 OpTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
758 OpTypedTransaction::Deposit(tx) => Self::Deposit(tx.into()),
759 }
760 }
761 }
762
763 #[derive(Debug, Serialize, Deserialize)]
765 pub struct OpTransactionSigned<'a> {
766 hash: TxHash,
767 signature: Signature,
768 transaction: OpTypedTransaction<'a>,
769 }
770
771 impl<'a> From<&'a super::OpTransactionSigned> for OpTransactionSigned<'a> {
772 fn from(value: &'a super::OpTransactionSigned) -> Self {
773 Self {
774 hash: *value.tx_hash(),
775 signature: value.signature,
776 transaction: OpTypedTransaction::from(&value.transaction),
777 }
778 }
779 }
780
781 impl<'a> From<OpTransactionSigned<'a>> for super::OpTransactionSigned {
782 fn from(value: OpTransactionSigned<'a>) -> Self {
783 Self {
784 hash: value.hash.into(),
785 signature: value.signature,
786 transaction: value.transaction.into(),
787 }
788 }
789 }
790
791 impl SerdeBincodeCompat for super::OpTransactionSigned {
792 type BincodeRepr<'a> = OpTransactionSigned<'a>;
793
794 fn as_repr(&self) -> Self::BincodeRepr<'_> {
795 self.into()
796 }
797
798 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
799 repr.into()
800 }
801 }
802}