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