Skip to main content

foundry_cli/opts/
tempo.rs

1use alloy_network::{Network, TransactionBuilder};
2use alloy_primitives::{Address, ruint::aliases::U256};
3use alloy_signer::Signature;
4use clap::Parser;
5use foundry_primitives::FoundryTransactionBuilder;
6use std::str::FromStr;
7
8/// CLI options for Tempo transactions.
9#[derive(Clone, Debug, Default, Parser)]
10#[command(next_help_heading = "Tempo")]
11pub struct TempoOpts {
12    /// Fee token address for Tempo transactions.
13    ///
14    /// When set, builds a Tempo (type 0x76) transaction that pays gas fees
15    /// in the specified token.
16    ///
17    /// If this is not set, the fee token is chosen according to network rules. See the Tempo docs
18    /// for more information.
19    #[arg(long = "tempo.fee-token")]
20    pub fee_token: Option<Address>,
21
22    /// Nonce sequence key for Tempo transactions.
23    ///
24    /// When set, builds a Tempo (type 0x76) transaction with the specified nonce sequence key.
25    ///
26    /// If this is not set, the protocol sequence key (0) will be used.
27    ///
28    /// For more information see <https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#parallelizable-nonces>.
29    #[arg(long = "tempo.seq")]
30    pub sequence_key: Option<U256>,
31
32    /// Sponsor (fee payer) signature for Tempo sponsored transactions.
33    ///
34    /// The sponsor signs the `fee_payer_signature_hash` to commit to paying gas fees
35    /// on behalf of the sender. Provide as a hex-encoded signature.
36    #[arg(long = "tempo.sponsor-sig", value_parser = parse_signature)]
37    pub sponsor_signature: Option<Signature>,
38
39    /// Print the sponsor signature hash and exit.
40    ///
41    /// Computes the `fee_payer_signature_hash` for the transaction so that a sponsor
42    /// knows what hash to sign. The transaction is not sent.
43    #[arg(long = "tempo.print-sponsor-hash")]
44    pub print_sponsor_hash: bool,
45
46    /// Access key ID for Tempo Keychain signature transactions.
47    ///
48    /// Used during gas estimation to override the key_id that would normally be
49    /// recovered from the signature.
50    #[arg(long = "tempo.key-id")]
51    pub key_id: Option<Address>,
52
53    /// Enable expiring nonce mode for Tempo transactions.
54    ///
55    /// Sets nonce to 0 and nonce_key to U256::MAX, enabling time-bounded transaction
56    /// validity via `--tempo.valid-before` and `--tempo.valid-after`.
57    #[arg(long = "tempo.expiring-nonce", requires = "valid_before")]
58    pub expiring_nonce: bool,
59
60    /// Upper bound timestamp for Tempo expiring nonce transactions.
61    ///
62    /// The transaction is only valid before this unix timestamp.
63    /// Requires `--tempo.expiring-nonce`.
64    #[arg(long = "tempo.valid-before")]
65    pub valid_before: Option<u64>,
66
67    /// Lower bound timestamp for Tempo expiring nonce transactions.
68    ///
69    /// The transaction is only valid after this unix timestamp.
70    /// Requires `--tempo.expiring-nonce`.
71    #[arg(long = "tempo.valid-after")]
72    pub valid_after: Option<u64>,
73}
74
75impl TempoOpts {
76    /// Returns `true` if any Tempo-specific option is set.
77    pub fn is_tempo(&self) -> bool {
78        self.fee_token.is_some()
79            || self.sequence_key.is_some()
80            || self.sponsor_signature.is_some()
81            || self.print_sponsor_hash
82            || self.key_id.is_some()
83            || self.expiring_nonce
84            || self.valid_before.is_some()
85            || self.valid_after.is_some()
86    }
87
88    /// Applies Tempo-specific options to a transaction request.
89    ///
90    /// All setters are no-ops for non-Tempo networks, so this is safe to call unconditionally.
91    pub fn apply<N: Network>(&self, tx: &mut N::TransactionRequest, nonce: Option<u64>)
92    where
93        N::TransactionRequest: FoundryTransactionBuilder<N>,
94    {
95        // Handle expiring nonce mode: sets nonce=0 and nonce_key=U256::MAX
96        if self.expiring_nonce {
97            tx.set_nonce(0);
98            tx.set_nonce_key(U256::MAX);
99        } else {
100            if let Some(nonce) = nonce {
101                tx.set_nonce(nonce);
102            }
103            if let Some(nonce_key) = self.sequence_key {
104                tx.set_nonce_key(nonce_key);
105            }
106        }
107
108        if let Some(fee_token) = self.fee_token {
109            tx.set_fee_token(fee_token);
110        }
111
112        if let Some(valid_before) = self.valid_before {
113            tx.set_valid_before(valid_before);
114        }
115        if let Some(valid_after) = self.valid_after {
116            tx.set_valid_after(valid_after);
117        }
118
119        if let Some(key_id) = self.key_id {
120            tx.set_key_id(key_id);
121        }
122
123        // Force AA tx type if sponsoring or printing sponsor hash.
124        if self.sponsor_signature.is_some() || self.print_sponsor_hash {
125            if tx.nonce_key().is_none() {
126                tx.set_nonce_key(U256::ZERO);
127            }
128            if let Some(sig) = self.sponsor_signature {
129                tx.set_fee_payer_signature(sig);
130            }
131        }
132    }
133}
134
135fn parse_signature(s: &str) -> Result<Signature, String> {
136    Signature::from_str(s).map_err(|e| format!("invalid signature: {e}"))
137}