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, provider::ProviderBuilder, tempo::print_resolved_fee_token_selection,
20};
21use std::{path::PathBuf, str::FromStr};
22use tempo_alloy::TempoNetwork;
23
24#[derive(Debug, Parser)]
26pub struct MakeTxArgs {
27 #[arg(value_parser = NameOrAddress::from_str)]
31 to: Option<NameOrAddress>,
32
33 sig: Option<String>,
35
36 #[arg(allow_negative_numbers = true)]
38 args: Vec<String>,
39
40 #[command(subcommand)]
41 command: Option<MakeTxSubcommands>,
42
43 #[command(flatten)]
44 tx: TransactionOpts,
45
46 #[arg(
48 long,
49 value_name = "BLOB_DATA_PATH",
50 conflicts_with = "legacy",
51 requires = "blob",
52 help_heading = "Transaction options"
53 )]
54 path: Option<PathBuf>,
55
56 #[command(flatten)]
57 eth: EthereumOpts,
58
59 #[arg(long)]
63 raw_unsigned: bool,
64
65 #[arg(long, requires = "from", conflicts_with = "raw_unsigned")]
67 ethsign: bool,
68}
69
70#[derive(Debug, Parser)]
71pub enum MakeTxSubcommands {
72 #[command(name = "--create")]
74 Create {
75 code: String,
77
78 sig: Option<String>,
80
81 #[arg(allow_negative_numbers = true)]
83 args: Vec<String>,
84 },
85}
86
87impl MakeTxArgs {
88 pub async fn run(self) -> Result<()> {
89 if self.tx.tempo.is_tempo() {
90 self.run_generic::<TempoNetwork>().await
91 } else {
92 self.run_generic::<Ethereum>().await
93 }
94 }
95
96 pub async fn run_generic<N: Network>(self) -> Result<()>
97 where
98 N::TxEnvelope: From<Signed<N::UnsignedTx>>,
99 N::UnsignedTx: SignableTransaction<Signature>,
100 N::TransactionRequest: FoundryTransactionBuilder<N>,
101 {
102 let Self { to, mut sig, mut args, command, mut tx, path, eth, raw_unsigned, ethsign } =
103 self;
104
105 let print_sponsor_hash = tx.tempo.print_sponsor_hash;
106 let expires_at = tx.tempo.resolve_expires();
107 let tempo_sponsor =
108 if print_sponsor_hash { None } else { tx.tempo.sponsor_config().await? };
109
110 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
111
112 let code = if let Some(MakeTxSubcommands::Create {
113 code,
114 sig: constructor_sig,
115 args: constructor_args,
116 }) = command
117 {
118 sig = constructor_sig;
119 args = constructor_args;
120 Some(code)
121 } else {
122 None
123 };
124
125 let config = eth.load_config()?;
126
127 let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
128
129 let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
133
134 let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config)
135 .await?
136 .with_to(to)
137 .await?
138 .with_code_sig_and_args(code, sig, args)
139 .await?
140 .with_blob_data(blob_data)?;
141 let chain = tx_builder.chain();
142
143 if print_sponsor_hash {
145 let signer = eth.wallet.signer().await?;
148 let from = signer.address();
149 let (tx, _) = tx_builder.build(from).await?;
150 let hash = tx.compute_sponsor_hash(from).ok_or_else(|| {
151 eyre::eyre!("This network does not support sponsored transactions")
152 })?;
153 print_scalar(format!("{hash:?}"))?;
154 return Ok(());
155 }
156
157 if let Some(ts) = expires_at {
158 sh_status!("Transaction expires at unix timestamp {ts}")?;
159 }
160
161 if raw_unsigned {
162 if eth.wallet.from.is_none() && tx.nonce.is_none() {
166 eyre::bail!(
167 "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce"
168 );
169 }
170 if tempo_sponsor.is_some() && eth.wallet.from.is_none() {
171 eyre::bail!(
172 "--tempo.sponsor requires --from for --raw-unsigned because the sponsor digest commits to the sender"
173 );
174 }
175
176 let from = eth.wallet.from.unwrap_or(Address::ZERO);
178
179 let (mut tx, _) = tx_builder.build(from).await?;
180 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
181 if let Some(sponsor) = &tempo_sponsor {
182 sponsor.attach_and_print::<N>(&mut tx, from).await?;
183 }
184 print_resolved_fee_token_selection(Some(chain), tx.fee_token())?;
185 let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing());
186
187 print_scalar(raw_tx)?;
188 return Ok(());
189 }
190
191 if ethsign {
192 let (mut tx, _) = tx_builder.build(config.sender).await?;
195 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
196 if let Some(sponsor) = &tempo_sponsor {
197 sponsor.attach_and_print::<N>(&mut tx, config.sender).await?;
198 }
199 print_resolved_fee_token_selection(Some(chain), tx.fee_token())?;
200 let signed_tx = provider.sign_transaction(tx).await?;
201
202 print_scalar(signed_tx)?;
203 return Ok(());
204 }
205
206 let signer = eth.wallet.signer().await?;
209 let from = signer.address();
210
211 tx::validate_from_address(eth.wallet.from, from)?;
212
213 let (mut tx, _) = tx_builder.build(&signer).await?;
214 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
215 if let Some(sponsor) = &tempo_sponsor {
216 sponsor.attach_and_print::<N>(&mut tx, from).await?;
217 }
218 print_resolved_fee_token_selection(Some(chain), tx.fee_token())?;
219
220 let tx = tx.build(&EthereumWallet::new(signer)).await?;
221
222 let signed_tx = format!("0x{}", hex::encode(tx.encoded_2718()));
223 print_scalar(signed_tx)?;
224
225 Ok(())
226 }
227}