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}