tempo_primitives/transaction/
envelope.rs

1use super::{fee_token::TxFeeToken, tt_signed::AASigned};
2use crate::{TempoTransaction, subblock::PartialValidatorKey};
3use alloy_consensus::{
4    EthereumTxEnvelope, SignableTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxEip7702,
5    TxLegacy, TxType, TypedTransaction,
6    error::{UnsupportedTransactionType, ValueError},
7    transaction::Either,
8};
9use alloy_primitives::{Address, B256, Bytes, Signature, SignatureError, TxKind, U256, hex};
10use core::fmt;
11use reth_primitives_traits::InMemorySize;
12
13/// TIP20 payment address prefix (14 bytes for payment classification)
14/// Same as TIP20_TOKEN_PREFIX but extended to 14 bytes for payment classification
15pub const TIP20_PAYMENT_PREFIX: [u8; 14] = hex!("20C0000000000000000000000000");
16
17/// Fake signature for Tempo system transactions.
18pub const TEMPO_SYSTEM_TX_SIGNATURE: Signature = Signature::new(U256::ZERO, U256::ZERO, false);
19
20/// Fake sender for Tempo system transactions.
21pub const TEMPO_SYSTEM_TX_SENDER: Address = Address::ZERO;
22
23/// Tempo transaction envelope containing all supported transaction types
24///
25/// Transaction types included:
26/// - Legacy transactions
27/// - EIP-2930 access list transactions
28/// - EIP-1559 dynamic fee transactions
29/// - EIP-7702 authorization list transactions
30/// - Tempo fee token transactions (0x77)
31#[derive(Clone, Debug, alloy_consensus::TransactionEnvelope)]
32#[envelope(
33    tx_type_name = TempoTxType,
34    typed = TempoTypedTransaction,
35    arbitrary_cfg(any(test, feature = "arbitrary")),
36    serde_cfg(feature = "serde")
37)]
38#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
39#[expect(clippy::large_enum_variant)]
40pub enum TempoTxEnvelope {
41    /// Legacy transaction (type 0x00)
42    #[envelope(ty = 0)]
43    Legacy(Signed<TxLegacy>),
44
45    /// EIP-2930 access list transaction (type 0x01)
46    #[envelope(ty = 1)]
47    Eip2930(Signed<TxEip2930>),
48
49    /// EIP-1559 dynamic fee transaction (type 0x02)
50    #[envelope(ty = 2)]
51    Eip1559(Signed<TxEip1559>),
52
53    /// EIP-7702 authorization list transaction (type 0x04)
54    #[envelope(ty = 4)]
55    Eip7702(Signed<TxEip7702>),
56
57    /// Tempo transaction (type 0x76)
58    #[envelope(ty = 0x76, typed = TempoTransaction)]
59    AA(AASigned),
60
61    /// Tempo fee token transaction (type 0x77)
62    #[envelope(ty = 0x77)]
63    FeeToken(Signed<TxFeeToken>),
64}
65
66impl TryFrom<TxType> for TempoTxType {
67    type Error = UnsupportedTransactionType<TxType>;
68
69    fn try_from(value: TxType) -> Result<Self, Self::Error> {
70        Ok(match value {
71            TxType::Legacy => Self::Legacy,
72            TxType::Eip2930 => Self::Eip2930,
73            TxType::Eip1559 => Self::Eip1559,
74            TxType::Eip4844 => return Err(UnsupportedTransactionType::new(TxType::Eip4844)),
75            TxType::Eip7702 => Self::Eip7702,
76        })
77    }
78}
79
80impl TryFrom<TempoTxType> for TxType {
81    type Error = UnsupportedTransactionType<TempoTxType>;
82
83    fn try_from(value: TempoTxType) -> Result<Self, Self::Error> {
84        Ok(match value {
85            TempoTxType::Legacy => Self::Legacy,
86            TempoTxType::Eip2930 => Self::Eip2930,
87            TempoTxType::Eip1559 => Self::Eip1559,
88            TempoTxType::Eip7702 => Self::Eip7702,
89            TempoTxType::FeeToken => {
90                return Err(UnsupportedTransactionType::new(TempoTxType::FeeToken));
91            }
92            TempoTxType::AA => {
93                return Err(UnsupportedTransactionType::new(TempoTxType::AA));
94            }
95        })
96    }
97}
98
99impl TempoTxEnvelope {
100    /// Returns the fee token preference if this is a fee token transaction
101    pub fn fee_token(&self) -> Option<Address> {
102        match self {
103            Self::FeeToken(tx) => tx.tx().fee_token,
104            Self::AA(tx) => tx.tx().fee_token,
105            _ => None,
106        }
107    }
108
109    /// Resolves fee payer for the transaction.
110    pub fn fee_payer(&self, sender: Address) -> Result<Address, SignatureError> {
111        match self {
112            Self::FeeToken(tx) => {
113                if let Some(fee_payer_signature) = tx.tx().fee_payer_signature {
114                    fee_payer_signature
115                        .recover_address_from_prehash(&tx.tx().fee_payer_signature_hash(sender))
116                } else {
117                    Ok(sender)
118                }
119            }
120            Self::AA(tx) => {
121                if let Some(fee_payer_signature) = tx.tx().fee_payer_signature {
122                    fee_payer_signature
123                        .recover_address_from_prehash(&tx.tx().fee_payer_signature_hash(sender))
124                } else {
125                    Ok(sender)
126                }
127            }
128            _ => Ok(sender),
129        }
130    }
131
132    /// Return the [`TempoTxType`] of the inner txn.
133    pub const fn tx_type(&self) -> TempoTxType {
134        match self {
135            Self::Legacy(_) => TempoTxType::Legacy,
136            Self::Eip2930(_) => TempoTxType::Eip2930,
137            Self::Eip1559(_) => TempoTxType::Eip1559,
138            Self::Eip7702(_) => TempoTxType::Eip7702,
139            Self::AA(_) => TempoTxType::AA,
140            Self::FeeToken(_) => TempoTxType::FeeToken,
141        }
142    }
143
144    /// Returns true if this is a fee token transaction
145    pub fn is_fee_token(&self) -> bool {
146        matches!(self, Self::FeeToken(_) | Self::AA(_))
147    }
148
149    /// Returns the authorization list if present (for EIP-7702 and FeeToken transactions)
150    pub fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
151        match self {
152            Self::Eip7702(tx) => Some(&tx.tx().authorization_list),
153            Self::FeeToken(tx) => Some(&tx.tx().authorization_list),
154            _ => None,
155        }
156    }
157
158    /// Returns the Tempo authorization list if present (for Tempo transactions)
159    pub fn tempo_authorization_list(
160        &self,
161    ) -> Option<&[crate::transaction::TempoSignedAuthorization]> {
162        match self {
163            Self::AA(tx) => Some(&tx.tx().tempo_authorization_list),
164            _ => None,
165        }
166    }
167
168    /// Returns true if this is a Tempo system transaction
169    pub fn is_system_tx(&self) -> bool {
170        matches!(self, Self::Legacy(tx) if tx.signature() == &TEMPO_SYSTEM_TX_SIGNATURE)
171    }
172
173    /// Returns true if this is a valid Tempo system transaction, i.e all gas fields and nonce are zero.
174    pub fn is_valid_system_tx(&self, chain_id: u64) -> bool {
175        self.max_fee_per_gas() == 0
176            && self.gas_limit() == 0
177            && self.value().is_zero()
178            && self.chain_id() == Some(chain_id)
179            && self.nonce() == 0
180    }
181
182    /// Classify a transaction as payment or non-payment.
183    ///
184    /// Currently uses classifier v1: transaction is a payment if the `to` address has the TIP20 prefix.
185    pub fn is_payment(&self) -> bool {
186        match self {
187            Self::Legacy(tx) => tx
188                .tx()
189                .to
190                .to()
191                .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
192            Self::Eip2930(tx) => tx
193                .tx()
194                .to
195                .to()
196                .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
197            Self::Eip1559(tx) => tx
198                .tx()
199                .to
200                .to()
201                .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
202            Self::Eip7702(tx) => tx.tx().to.starts_with(&TIP20_PAYMENT_PREFIX),
203            Self::FeeToken(tx) => tx
204                .tx()
205                .to
206                .to()
207                .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
208            Self::AA(tx) => tx.tx().calls.iter().all(|call| {
209                call.to
210                    .to()
211                    .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX))
212            }),
213        }
214    }
215
216    /// Returns the proposer of the subblock if this is a subblock transaction.
217    pub fn subblock_proposer(&self) -> Option<PartialValidatorKey> {
218        let Self::AA(tx) = &self else { return None };
219        tx.tx().subblock_proposer()
220    }
221
222    /// Returns the [`AASigned`] transaction if this is a Tempo transaction.
223    pub fn as_aa(&self) -> Option<&AASigned> {
224        match self {
225            Self::AA(tx) => Some(tx),
226            _ => None,
227        }
228    }
229
230    /// Returns the nonce key of this transaction if it's an [`AASigned`] transaction.
231    pub fn nonce_key(&self) -> Option<U256> {
232        self.as_aa().map(|tx| tx.tx().nonce_key)
233    }
234
235    /// Returns true if this is a Tempo transaction
236    pub fn is_aa(&self) -> bool {
237        matches!(self, Self::AA(_))
238    }
239
240    /// Returns iterator over the calls in the transaction.
241    pub fn calls(&self) -> impl Iterator<Item = (TxKind, &Bytes)> {
242        if let Some(aa) = self.as_aa() {
243            Either::Left(aa.tx().calls.iter().map(|call| (call.to, &call.input)))
244        } else {
245            Either::Right(core::iter::once((self.kind(), self.input())))
246        }
247    }
248}
249
250impl alloy_consensus::transaction::SignerRecoverable for TempoTxEnvelope {
251    fn recover_signer(
252        &self,
253    ) -> Result<alloy_primitives::Address, alloy_consensus::crypto::RecoveryError> {
254        match self {
255            Self::Legacy(tx) if tx.signature() == &TEMPO_SYSTEM_TX_SIGNATURE => Ok(Address::ZERO),
256            Self::Legacy(tx) => alloy_consensus::transaction::SignerRecoverable::recover_signer(tx),
257            Self::Eip2930(tx) => {
258                alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
259            }
260            Self::Eip1559(tx) => {
261                alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
262            }
263            Self::Eip7702(tx) => {
264                alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
265            }
266            Self::FeeToken(tx) => {
267                alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
268            }
269            Self::AA(tx) => alloy_consensus::transaction::SignerRecoverable::recover_signer(tx),
270        }
271    }
272
273    fn recover_signer_unchecked(
274        &self,
275    ) -> Result<alloy_primitives::Address, alloy_consensus::crypto::RecoveryError> {
276        match self {
277            Self::Legacy(tx) if tx.signature() == &TEMPO_SYSTEM_TX_SIGNATURE => Ok(Address::ZERO),
278            Self::Legacy(tx) => {
279                alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
280            }
281            Self::Eip2930(tx) => {
282                alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
283            }
284            Self::Eip1559(tx) => {
285                alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
286            }
287            Self::Eip7702(tx) => {
288                alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
289            }
290            Self::FeeToken(tx) => {
291                alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
292            }
293            Self::AA(tx) => {
294                alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
295            }
296        }
297    }
298}
299
300impl reth_primitives_traits::InMemorySize for TempoTxEnvelope {
301    fn size(&self) -> usize {
302        match self {
303            Self::Legacy(tx) => reth_primitives_traits::InMemorySize::size(tx),
304            Self::Eip2930(tx) => reth_primitives_traits::InMemorySize::size(tx),
305            Self::Eip1559(tx) => reth_primitives_traits::InMemorySize::size(tx),
306            Self::Eip7702(tx) => reth_primitives_traits::InMemorySize::size(tx),
307            Self::AA(tx) => reth_primitives_traits::InMemorySize::size(tx),
308            Self::FeeToken(tx) => reth_primitives_traits::InMemorySize::size(tx),
309        }
310    }
311}
312
313impl alloy_consensus::transaction::TxHashRef for TempoTxEnvelope {
314    fn tx_hash(&self) -> &B256 {
315        match self {
316            Self::Legacy(tx) => tx.hash(),
317            Self::Eip2930(tx) => tx.hash(),
318            Self::Eip1559(tx) => tx.hash(),
319            Self::Eip7702(tx) => tx.hash(),
320            Self::AA(tx) => tx.hash(),
321            Self::FeeToken(tx) => tx.hash(),
322        }
323    }
324}
325
326impl reth_primitives_traits::SignedTransaction for TempoTxEnvelope {}
327
328impl InMemorySize for TempoTxType {
329    fn size(&self) -> usize {
330        core::mem::size_of::<Self>()
331    }
332}
333
334impl fmt::Display for TempoTxType {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        match self {
337            Self::Legacy => write!(f, "Legacy"),
338            Self::Eip2930 => write!(f, "EIP-2930"),
339            Self::Eip1559 => write!(f, "EIP-1559"),
340            Self::Eip7702 => write!(f, "EIP-7702"),
341            Self::AA => write!(f, "AA"),
342            Self::FeeToken => write!(f, "FeeToken"),
343        }
344    }
345}
346
347impl<Eip4844> TryFrom<EthereumTxEnvelope<Eip4844>> for TempoTxEnvelope {
348    type Error = ValueError<EthereumTxEnvelope<Eip4844>>;
349
350    fn try_from(value: EthereumTxEnvelope<Eip4844>) -> Result<Self, Self::Error> {
351        match value {
352            EthereumTxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
353            EthereumTxEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
354            tx @ EthereumTxEnvelope::Eip4844(_) => Err(ValueError::new_static(
355                tx,
356                "EIP-4844 transactions are not supported",
357            )),
358            EthereumTxEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
359            EthereumTxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
360        }
361    }
362}
363
364impl From<Signed<TxLegacy>> for TempoTxEnvelope {
365    fn from(value: Signed<TxLegacy>) -> Self {
366        Self::Legacy(value)
367    }
368}
369
370impl From<Signed<TxEip2930>> for TempoTxEnvelope {
371    fn from(value: Signed<TxEip2930>) -> Self {
372        Self::Eip2930(value)
373    }
374}
375
376impl From<Signed<TxEip1559>> for TempoTxEnvelope {
377    fn from(value: Signed<TxEip1559>) -> Self {
378        Self::Eip1559(value)
379    }
380}
381
382impl From<Signed<TxEip7702>> for TempoTxEnvelope {
383    fn from(value: Signed<TxEip7702>) -> Self {
384        Self::Eip7702(value)
385    }
386}
387
388impl From<Signed<TxFeeToken>> for TempoTxEnvelope {
389    fn from(value: Signed<TxFeeToken>) -> Self {
390        Self::FeeToken(value)
391    }
392}
393
394impl From<AASigned> for TempoTxEnvelope {
395    fn from(value: AASigned) -> Self {
396        Self::AA(value)
397    }
398}
399
400impl TempoTypedTransaction {
401    /// Converts this typed transaction into a signed [`TempoTxEnvelope`]
402    pub fn into_envelope(self, sig: Signature) -> TempoTxEnvelope {
403        match self {
404            Self::Legacy(tx) => tx.into_signed(sig).into(),
405            Self::Eip2930(tx) => tx.into_signed(sig).into(),
406            Self::Eip1559(tx) => tx.into_signed(sig).into(),
407            Self::Eip7702(tx) => tx.into_signed(sig).into(),
408            Self::AA(tx) => tx.into_signed(sig.into()).into(),
409            Self::FeeToken(tx) => tx.into_signed(sig).into(),
410        }
411    }
412
413    /// Returns a dyn mutable reference to the underlying transaction
414    pub fn as_dyn_signable_mut(&mut self) -> &mut dyn SignableTransaction<Signature> {
415        match self {
416            Self::Legacy(tx) => tx,
417            Self::Eip2930(tx) => tx,
418            Self::Eip1559(tx) => tx,
419            Self::Eip7702(tx) => tx,
420            Self::AA(tx) => tx,
421            Self::FeeToken(tx) => tx,
422        }
423    }
424}
425
426impl TryFrom<TypedTransaction> for TempoTypedTransaction {
427    type Error = UnsupportedTransactionType<TxType>;
428
429    fn try_from(value: TypedTransaction) -> Result<Self, Self::Error> {
430        Ok(match value {
431            TypedTransaction::Legacy(tx) => Self::Legacy(tx),
432            TypedTransaction::Eip2930(tx) => Self::Eip2930(tx),
433            TypedTransaction::Eip1559(tx) => Self::Eip1559(tx),
434            TypedTransaction::Eip4844(..) => {
435                return Err(UnsupportedTransactionType::new(TxType::Eip4844));
436            }
437            TypedTransaction::Eip7702(tx) => Self::Eip7702(tx),
438        })
439    }
440}
441
442impl From<TempoTxEnvelope> for TempoTypedTransaction {
443    fn from(value: TempoTxEnvelope) -> Self {
444        match value {
445            TempoTxEnvelope::Legacy(tx) => Self::Legacy(tx.into_parts().0),
446            TempoTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.into_parts().0),
447            TempoTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.into_parts().0),
448            TempoTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.into_parts().0),
449            TempoTxEnvelope::FeeToken(tx) => Self::FeeToken(tx.into_parts().0),
450            TempoTxEnvelope::AA(tx) => Self::AA(tx.into_parts().0),
451        }
452    }
453}
454
455impl From<TxFeeToken> for TempoTypedTransaction {
456    fn from(value: TxFeeToken) -> Self {
457        Self::FeeToken(value)
458    }
459}
460
461impl From<TempoTransaction> for TempoTypedTransaction {
462    fn from(value: TempoTransaction) -> Self {
463        Self::AA(value)
464    }
465}
466
467#[cfg(feature = "rpc")]
468impl reth_rpc_convert::SignableTxRequest<TempoTxEnvelope>
469    for alloy_rpc_types_eth::TransactionRequest
470{
471    async fn try_build_and_sign(
472        self,
473        signer: impl alloy_network::TxSigner<alloy_primitives::Signature> + Send,
474    ) -> Result<TempoTxEnvelope, reth_rpc_convert::SignTxRequestError> {
475        reth_rpc_convert::SignableTxRequest::<
476            EthereumTxEnvelope<alloy_consensus::TxEip4844>,
477        >::try_build_and_sign(self, signer)
478        .await
479        .and_then(|tx| {
480            tx.try_into()
481                .map_err(|_| reth_rpc_convert::SignTxRequestError::InvalidTransactionRequest)
482        })
483    }
484}
485
486#[cfg(feature = "rpc")]
487impl reth_rpc_convert::TryIntoSimTx<TempoTxEnvelope> for alloy_rpc_types_eth::TransactionRequest {
488    fn try_into_sim_tx(self) -> Result<TempoTxEnvelope, ValueError<Self>> {
489        let tx = self.clone().build_typed_simulate_transaction()?;
490        tx.try_into()
491            .map_err(|_| ValueError::new_static(self, "Invalid transaction request"))
492    }
493}
494
495#[cfg(feature = "serde-bincode-compat")]
496impl reth_primitives_traits::serde_bincode_compat::RlpBincode for TempoTxEnvelope {}
497
498#[cfg(feature = "reth-codec")]
499mod codec {
500    use crate::{TempoSignature, TempoTransaction};
501
502    use super::*;
503    use alloy_eips::eip2718::EIP7702_TX_TYPE_ID;
504    use alloy_primitives::{
505        Bytes, Signature,
506        bytes::{self, BufMut},
507    };
508    use reth_codecs::{
509        Compact,
510        alloy::transaction::{CompactEnvelope, Envelope},
511        txtype::{
512            COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559,
513            COMPACT_IDENTIFIER_EIP2930, COMPACT_IDENTIFIER_LEGACY,
514        },
515    };
516
517    impl reth_codecs::alloy::transaction::FromTxCompact for TempoTxEnvelope {
518        type TxType = TempoTxType;
519
520        fn from_tx_compact(
521            buf: &[u8],
522            tx_type: Self::TxType,
523            signature: Signature,
524        ) -> (Self, &[u8]) {
525            use alloy_consensus::Signed;
526            use reth_codecs::Compact;
527
528            match tx_type {
529                TempoTxType::Legacy => {
530                    let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
531                    let tx = Signed::new_unhashed(tx, signature);
532                    (Self::Legacy(tx), buf)
533                }
534                TempoTxType::Eip2930 => {
535                    let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
536                    let tx = Signed::new_unhashed(tx, signature);
537                    (Self::Eip2930(tx), buf)
538                }
539                TempoTxType::Eip1559 => {
540                    let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
541                    let tx = Signed::new_unhashed(tx, signature);
542                    (Self::Eip1559(tx), buf)
543                }
544                TempoTxType::Eip7702 => {
545                    let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
546                    let tx = Signed::new_unhashed(tx, signature);
547                    (Self::Eip7702(tx), buf)
548                }
549                TempoTxType::AA => {
550                    let (tx, buf) = TempoTransaction::from_compact(buf, buf.len());
551                    // For Tempo transactions, we need to decode the signature bytes as TempoSignature
552                    let (sig_bytes, buf) = Bytes::from_compact(buf, buf.len());
553                    let aa_sig = TempoSignature::from_bytes(&sig_bytes)
554                        .map_err(|e| panic!("Failed to decode AA signature: {e}"))
555                        .unwrap();
556                    let tx = AASigned::new_unhashed(tx, aa_sig);
557                    (Self::AA(tx), buf)
558                }
559                TempoTxType::FeeToken => {
560                    let (tx, buf) = TxFeeToken::from_compact(buf, buf.len());
561                    let tx = Signed::new_unhashed(tx, signature);
562                    (Self::FeeToken(tx), buf)
563                }
564            }
565        }
566    }
567
568    impl reth_codecs::alloy::transaction::ToTxCompact for TempoTxEnvelope {
569        fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) {
570            match self {
571                Self::Legacy(tx) => tx.tx().to_compact(buf),
572                Self::Eip2930(tx) => tx.tx().to_compact(buf),
573                Self::Eip1559(tx) => tx.tx().to_compact(buf),
574                Self::Eip7702(tx) => tx.tx().to_compact(buf),
575                Self::AA(tx) => {
576                    let mut len = tx.tx().to_compact(buf);
577                    // Also encode the TempoSignature as Bytes
578                    len += tx.signature().to_bytes().to_compact(buf);
579                    len
580                }
581                Self::FeeToken(tx) => tx.tx().to_compact(buf),
582            };
583        }
584    }
585
586    impl Envelope for TempoTxEnvelope {
587        fn signature(&self) -> &Signature {
588            match self {
589                Self::Legacy(tx) => tx.signature(),
590                Self::Eip2930(tx) => tx.signature(),
591                Self::Eip1559(tx) => tx.signature(),
592                Self::Eip7702(tx) => tx.signature(),
593                Self::AA(_tx) => {
594                    // TODO: Will this work?
595                    &TEMPO_SYSTEM_TX_SIGNATURE
596                }
597                Self::FeeToken(tx) => tx.signature(),
598            }
599        }
600
601        fn tx_type(&self) -> Self::TxType {
602            Self::tx_type(self)
603        }
604    }
605
606    impl Compact for TempoTxType {
607        fn to_compact<B>(&self, buf: &mut B) -> usize
608        where
609            B: BufMut + AsMut<[u8]>,
610        {
611            match self {
612                Self::Legacy => COMPACT_IDENTIFIER_LEGACY,
613                Self::Eip2930 => COMPACT_IDENTIFIER_EIP2930,
614                Self::Eip1559 => COMPACT_IDENTIFIER_EIP1559,
615                Self::Eip7702 => {
616                    buf.put_u8(EIP7702_TX_TYPE_ID);
617                    COMPACT_EXTENDED_IDENTIFIER_FLAG
618                }
619                Self::AA => {
620                    buf.put_u8(crate::transaction::TEMPO_TX_TYPE_ID);
621                    COMPACT_EXTENDED_IDENTIFIER_FLAG
622                }
623                Self::FeeToken => {
624                    buf.put_u8(crate::transaction::FEE_TOKEN_TX_TYPE_ID);
625                    COMPACT_EXTENDED_IDENTIFIER_FLAG
626                }
627            }
628        }
629
630        // For backwards compatibility purposes only 2 bits of the type are encoded in the identifier
631        // parameter. In the case of a [`COMPACT_EXTENDED_IDENTIFIER_FLAG`], the full transaction type
632        // is read from the buffer as a single byte.
633        fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
634            use bytes::Buf;
635            (
636                match identifier {
637                    COMPACT_IDENTIFIER_LEGACY => Self::Legacy,
638                    COMPACT_IDENTIFIER_EIP2930 => Self::Eip2930,
639                    COMPACT_IDENTIFIER_EIP1559 => Self::Eip1559,
640                    COMPACT_EXTENDED_IDENTIFIER_FLAG => {
641                        let extended_identifier = buf.get_u8();
642                        match extended_identifier {
643                            EIP7702_TX_TYPE_ID => Self::Eip7702,
644                            crate::transaction::TEMPO_TX_TYPE_ID => Self::AA,
645                            crate::transaction::FEE_TOKEN_TX_TYPE_ID => Self::FeeToken,
646                            _ => panic!("Unsupported TxType identifier: {extended_identifier}"),
647                        }
648                    }
649                    _ => panic!("Unknown identifier for TxType: {identifier}"),
650                },
651                buf,
652            )
653        }
654    }
655
656    impl Compact for TempoTxEnvelope {
657        fn to_compact<B>(&self, buf: &mut B) -> usize
658        where
659            B: BufMut + AsMut<[u8]>,
660        {
661            CompactEnvelope::to_compact(self, buf)
662        }
663
664        fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
665            CompactEnvelope::from_compact(buf, len)
666        }
667    }
668
669    impl reth_db_api::table::Compress for TempoTxEnvelope {
670        type Compressed = Vec<u8>;
671
672        fn compress_to_buf<B: alloy_primitives::bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
673            let _ = Compact::to_compact(self, buf);
674        }
675    }
676
677    impl reth_db_api::table::Decompress for TempoTxEnvelope {
678        fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
679            let (obj, _) = Compact::from_compact(value, value.len());
680            Ok(obj)
681        }
682    }
683}
684
685#[cfg(test)]
686mod tests {
687    use super::*;
688    use alloy_primitives::{Signature, TxKind, address};
689
690    #[test]
691    fn test_fee_token_access() {
692        let fee_token_tx = TxFeeToken {
693            fee_token: Some(Address::ZERO),
694            ..Default::default()
695        };
696        let signature = Signature::new(
697            alloy_primitives::U256::ZERO,
698            alloy_primitives::U256::ZERO,
699            false,
700        );
701        let signed = Signed::new_unhashed(fee_token_tx, signature);
702        let envelope = TempoTxEnvelope::FeeToken(signed);
703
704        assert!(envelope.is_fee_token());
705        assert_eq!(envelope.fee_token(), Some(Address::ZERO));
706    }
707
708    #[test]
709    fn test_non_fee_token_access() {
710        let legacy_tx = TxLegacy::default();
711        let signature = Signature::new(
712            alloy_primitives::U256::ZERO,
713            alloy_primitives::U256::ZERO,
714            false,
715        );
716        let signed = Signed::new_unhashed(legacy_tx, signature);
717        let envelope = TempoTxEnvelope::Legacy(signed);
718
719        assert!(!envelope.is_fee_token());
720        assert_eq!(envelope.fee_token(), None);
721    }
722
723    #[test]
724    fn test_payment_classification_with_tip20_prefix() {
725        // Create an address with TIP20 prefix
726        let payment_addr = address!("20c0000000000000000000000000000000000001");
727        let tx = TxFeeToken {
728            to: TxKind::Call(payment_addr),
729            gas_limit: 21000,
730            ..Default::default()
731        };
732        let signed = Signed::new_unhashed(tx, Signature::test_signature());
733        let envelope = TempoTxEnvelope::FeeToken(signed);
734
735        assert!(envelope.is_payment());
736    }
737
738    #[test]
739    fn test_payment_classification_without_tip20_prefix() {
740        // Create an address without TIP20 prefix
741        let non_payment_addr = address!("1234567890123456789012345678901234567890");
742        let tx = TxFeeToken {
743            to: TxKind::Call(non_payment_addr),
744            gas_limit: 21000,
745            ..Default::default()
746        };
747        let signed = Signed::new_unhashed(tx, Signature::test_signature());
748        let envelope = TempoTxEnvelope::FeeToken(signed);
749
750        assert!(!envelope.is_payment());
751    }
752
753    #[test]
754    fn test_payment_classification_no_to_address() {
755        // Create a transaction with no `to` address (contract creation)
756        let tx = TxFeeToken {
757            to: TxKind::Create,
758            gas_limit: 21000,
759            ..Default::default()
760        };
761        let signed = Signed::new_unhashed(tx, Signature::test_signature());
762        let envelope = TempoTxEnvelope::FeeToken(signed);
763
764        assert!(!envelope.is_payment());
765    }
766
767    #[test]
768    fn test_payment_classification_partial_match() {
769        // Create an address that partially matches but not completely
770        let partial_match_addr = address!("20c0000000000000000000000000000100000000");
771        let tx = TxFeeToken {
772            to: TxKind::Call(partial_match_addr),
773            gas_limit: 21000,
774            ..Default::default()
775        };
776        let signed = Signed::new_unhashed(tx, Signature::test_signature());
777        let envelope = TempoTxEnvelope::FeeToken(signed);
778
779        // This should still be classified as payment since first 14 bytes match
780        assert!(envelope.is_payment());
781    }
782
783    #[test]
784    fn test_payment_classification_different_prefix() {
785        // Create an address with a different prefix
786        let different_prefix_addr = address!("30c0000000000000000000000000000000000001");
787        let tx = TxFeeToken {
788            to: TxKind::Call(different_prefix_addr),
789            gas_limit: 21000,
790            ..Default::default()
791        };
792        let signed = Signed::new_unhashed(tx, Signature::test_signature());
793        let envelope = TempoTxEnvelope::FeeToken(signed);
794
795        assert!(!envelope.is_payment());
796    }
797
798    #[test]
799    fn test_payment_classification_legacy_tx() {
800        // Test with legacy transaction type
801        let payment_addr = address!("20c0000000000000000000000000000000000001");
802        let tx = TxLegacy {
803            to: TxKind::Call(payment_addr),
804            gas_limit: 21000,
805            ..Default::default()
806        };
807        let signed = Signed::new_unhashed(tx, Signature::test_signature());
808        let envelope = TempoTxEnvelope::Legacy(signed);
809
810        assert!(envelope.is_payment());
811    }
812}