1use super::auth::{confirm_and_build, confirm_and_build_with_tempo_wallet};
2use crate::{
3 tempo,
4 tx::{self, CastTxBuilder},
5};
6use alloy_consensus::{SignableTransaction, Signed};
7use alloy_eips::Encodable2718;
8use alloy_ens::NameOrAddress;
9use alloy_network::{Ethereum, EthereumWallet, Network, NetworkTransactionBuilder};
10use alloy_primitives::{Address, hex};
11use alloy_provider::Provider;
12use alloy_signer::{Signature, Signer};
13use clap::Parser;
14use eyre::Result;
15use foundry_cli::{
16 json::print_scalar,
17 opts::{EthereumOpts, TransactionOpts},
18 utils::{LoadConfig, resolve_lane},
19};
20use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder};
21use foundry_wallets::{TempoAccountsWallet, WalletSigner};
22use std::{path::PathBuf, str::FromStr};
23use tempo_alloy::TempoNetwork;
24
25#[cfg(feature = "base")]
26use base_common_network::Base;
27
28#[derive(Debug, Parser)]
30pub struct MakeTxArgs {
31 #[arg(value_parser = NameOrAddress::from_str)]
35 to: Option<NameOrAddress>,
36
37 sig: Option<String>,
39
40 #[arg(allow_negative_numbers = true)]
42 args: Vec<String>,
43
44 #[command(subcommand)]
45 command: Option<MakeTxSubcommands>,
46
47 #[command(flatten)]
48 tx: TransactionOpts,
49
50 #[arg(long)]
52 force: bool,
53
54 #[arg(
56 long,
57 value_name = "BLOB_DATA_PATH",
58 conflicts_with = "legacy",
59 requires = "blob",
60 help_heading = "Transaction options"
61 )]
62 path: Option<PathBuf>,
63
64 #[command(flatten)]
65 eth: EthereumOpts,
66
67 #[arg(long)]
71 raw_unsigned: bool,
72
73 #[arg(long, requires = "from", conflicts_with = "raw_unsigned")]
75 ethsign: bool,
76
77 #[arg(
79 long,
80 value_name = "SIGNATURE",
81 requires = "from",
82 conflicts_with_all = ["raw_unsigned", "ethsign"]
83 )]
84 signature: Option<Signature>,
85}
86
87#[derive(Debug, Parser)]
88pub enum MakeTxSubcommands {
89 #[command(name = "--create")]
91 Create {
92 code: String,
94
95 sig: Option<String>,
97
98 #[arg(allow_negative_numbers = true)]
100 args: Vec<String>,
101 },
102}
103
104impl MakeTxArgs {
105 pub async fn run(self) -> Result<()> {
106 if self.tx.tempo.sponsor_url.is_some() {
107 eyre::bail!(
108 "--sponsor-url is not supported by cast mktx; use --tempo.sponsor with \
109 --tempo.sponsor-signer or --tempo.sponsor-sig"
110 );
111 }
112
113 let (network, signer, access_key) =
114 tempo::resolve_transaction_network_and_signer(&self.tx.tempo, &self.eth).await?;
115 if network.is_tempo() {
116 return self.run_generic::<TempoNetwork>(signer, access_key).await;
117 }
118
119 #[cfg(feature = "base")]
120 if network.is_base() {
121 super::validate_base_transaction_options(&self.tx)?;
122 return self.run_generic::<Base>(signer, None).await;
123 }
124
125 self.run_generic::<Ethereum>(signer, None).await
126 }
127
128 async fn run_generic<N: Network>(
129 self,
130 pre_resolved_signer: Option<WalletSigner>,
131 pre_resolved_access_key: Option<TempoAccountsWallet>,
132 ) -> Result<()>
133 where
134 N::TxEnvelope: From<Signed<N::UnsignedTx>>,
135 N::UnsignedTx: SignableTransaction<Signature>,
136 N::TransactionRequest: FoundryTransactionBuilder<N>,
137 {
138 let Self {
139 to,
140 mut sig,
141 mut args,
142 command,
143 mut tx,
144 force,
145 path,
146 eth,
147 raw_unsigned,
148 ethsign,
149 signature,
150 } = self;
151 let has_session = tx.tempo.session_id()?.is_some();
152
153 let print_sponsor_hash = tx.tempo.print_sponsor_hash;
154 let sponsor_fee_payer = tx.tempo.sponsor;
155 let expires_at = tx.tempo.resolve_expires();
156 let tempo_sponsor =
157 if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? };
158
159 let blob_data = path.map(std::fs::read).transpose()?;
160
161 let code = if let Some(MakeTxSubcommands::Create {
162 code,
163 sig: constructor_sig,
164 args: constructor_args,
165 }) = command
166 {
167 sig = constructor_sig;
168 args = constructor_args;
169 Some(code)
170 } else {
171 None
172 };
173
174 let config = eth.load_config()?;
175 let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
176 let fee_provider = (!config.eth_rpc_curl).then_some(&provider);
178
179 let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
182 let lane = resolved_lane.as_ref();
183
184 let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config)
185 .await?
186 .with_to(to)
187 .await?
188 .with_code_sig_and_args(code, sig, args)
189 .await?
190 .with_blob_data(blob_data)?;
191 let chain = tx_builder.chain();
192 let (signer, access_key) = if has_session || pre_resolved_access_key.is_some() {
193 tempo::resolve_session_or_wallet_signer(&tx.tempo, ð.wallet, chain.id()).await?
194 } else {
195 (pre_resolved_signer, None)
196 };
197
198 if print_sponsor_hash {
200 let (mut tx, from) = if let Some(access_key) = access_key {
204 let Some((tx, prepared)) =
205 confirm_and_build_with_tempo_wallet(tx_builder, &access_key, force, None)
206 .await?
207 else {
208 return Ok(());
209 };
210 (tx, prepared.account())
211 } else {
212 let signer = match signer {
213 Some(signer) => signer,
214 None => eth.wallet.signer().await?,
215 };
216 let Some(tx) = confirm_and_build(tx_builder, &signer, force, None, false).await?
217 else {
218 return Ok(());
219 };
220 (tx, signer.address())
221 };
222 let hash =
223 tempo::sponsor_hash(fee_provider, chain, &mut tx, from, sponsor_fee_payer).await?;
224 return print_scalar(format!("{hash:?}"));
225 }
226
227 tempo::print_expires(expires_at)?;
228
229 if raw_unsigned {
230 if eth.wallet.from.is_none() && tx.nonce.is_none() {
233 eyre::bail!(
234 "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce"
235 );
236 }
237 if tempo_sponsor.is_some() && eth.wallet.from.is_none() {
238 eyre::bail!(
239 "--tempo.sponsor requires --from for --raw-unsigned because the sponsor digest commits to the sender"
240 );
241 }
242
243 let from = eth.wallet.from.unwrap_or(Address::ZERO);
245 let Some(mut tx) = confirm_and_build(tx_builder, from, force, lane, false).await?
246 else {
247 return Ok(());
248 };
249 tempo::apply_fee_payment::<N, _>(
250 tempo_sponsor.as_ref(),
251 fee_provider,
252 chain,
253 &mut tx,
254 from,
255 )
256 .await?;
257 return print_scalar(hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()));
258 }
259
260 if let Some(signature) = signature {
261 let signature = signature.normalized_s();
262 let from = eth.wallet.from.expect("required by clap");
263 let Some(mut tx) = confirm_and_build(tx_builder, from, force, lane, false).await?
264 else {
265 return Ok(());
266 };
267 tempo::apply_fee_payment::<N, _>(
268 tempo_sponsor.as_ref(),
269 fee_provider,
270 chain,
271 &mut tx,
272 from,
273 )
274 .await?;
275
276 let tx = tx.build_unsigned()?;
277 let recovered = signature.recover_address_from_prehash(&tx.signature_hash())?;
278 if recovered != from {
279 eyre::bail!(
280 "The provided signature recovers to {recovered}, which does not match the specified sender {from}"
281 );
282 }
283
284 let tx = N::TxEnvelope::from(tx.into_signed(signature));
285 return print_scalar(hex::encode_prefixed(tx.encoded_2718()));
286 }
287
288 if ethsign {
289 let Some(mut tx) =
292 confirm_and_build(tx_builder, config.sender, force, lane, true).await?
293 else {
294 return Ok(());
295 };
296 tempo::apply_fee_payment::<N, _>(
297 tempo_sponsor.as_ref(),
298 fee_provider,
299 chain,
300 &mut tx,
301 config.sender,
302 )
303 .await?;
304 return print_scalar(provider.sign_transaction(tx).await?);
305 }
306
307 let signed_tx = if let Some(access_key) = access_key {
309 let Some((mut tx, prepared)) =
310 confirm_and_build_with_tempo_wallet(tx_builder, &access_key, force, lane).await?
311 else {
312 return Ok(());
313 };
314 tempo::apply_fee_payment::<N, _>(
315 tempo_sponsor.as_ref(),
316 fee_provider,
317 chain,
318 &mut tx,
319 prepared.account(),
320 )
321 .await?;
322 tx.sign_with_tempo_wallet(&prepared).await?
323 } else {
324 let (signer, from) = tx::resolve_send_signer(signer, ð).await?;
325 let Some(mut tx) = confirm_and_build(tx_builder, &signer, force, lane, false).await?
326 else {
327 return Ok(());
328 };
329 tempo::apply_fee_payment::<N, _>(
330 tempo_sponsor.as_ref(),
331 fee_provider,
332 chain,
333 &mut tx,
334 from,
335 )
336 .await?;
337 tx.build(&EthereumWallet::new(signer)).await?.encoded_2718()
338 };
339
340 print_scalar(hex::encode_prefixed(signed_tx))
341 }
342}