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