1use std::{path::PathBuf, str::FromStr, time::Duration};
2
3use alloy_consensus::{SignableTransaction, Signed};
4use alloy_ens::NameOrAddress;
5use alloy_network::{AnyNetwork, EthereumWallet, Network};
6use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder};
7use alloy_signer::{Signature, Signer};
8use clap::Parser;
9use eyre::{Result, eyre};
10use foundry_cli::{opts::TransactionOpts, utils::LoadConfig};
11use foundry_common::{
12 fmt::{UIfmt, UIfmtReceiptExt},
13 provider::ProviderBuilder,
14};
15use foundry_primitives::FoundryTransactionBuilder;
16use tempo_alloy::TempoNetwork;
17
18use crate::tx::{self, CastTxBuilder, CastTxSender, SendTxOpts};
19
20#[derive(Debug, Parser)]
22pub struct SendTxArgs {
23 #[arg(value_parser = NameOrAddress::from_str)]
27 to: Option<NameOrAddress>,
28
29 sig: Option<String>,
31
32 #[arg(allow_negative_numbers = true)]
34 args: Vec<String>,
35
36 #[arg(
38 long,
39 conflicts_with_all = &["sig", "args"]
40 )]
41 data: Option<String>,
42
43 #[command(flatten)]
44 send_tx: SendTxOpts,
45
46 #[command(subcommand)]
47 command: Option<SendTxSubcommands>,
48
49 #[arg(long, requires = "from")]
51 unlocked: bool,
52
53 #[command(flatten)]
54 tx: TransactionOpts,
55
56 #[arg(
58 long,
59 value_name = "BLOB_DATA_PATH",
60 conflicts_with = "legacy",
61 requires = "blob",
62 help_heading = "Transaction options"
63 )]
64 path: Option<PathBuf>,
65}
66
67#[derive(Debug, Parser)]
68pub enum SendTxSubcommands {
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 SendTxArgs {
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::<AnyNetwork>().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 N::ReceiptResponse: UIfmt + UIfmtReceiptExt,
99 {
100 let Self { to, mut sig, mut args, data, send_tx, tx, command, unlocked, path } = self;
101
102 let print_sponsor_hash = tx.tempo.print_sponsor_hash;
103
104 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
105
106 if let Some(data) = data {
107 sig = Some(data);
108 }
109
110 let code = if let Some(SendTxSubcommands::Create {
111 code,
112 sig: constructor_sig,
113 args: constructor_args,
114 }) = command
115 {
116 if to.is_none() && !tx.auth.is_empty() {
119 return Err(eyre!(
120 "EIP-7702 transactions can't be CREATE transactions and require a destination address"
121 ));
122 }
123 if to.is_none() && blob_data.is_some() {
126 return Err(eyre!(
127 "EIP-4844 transactions can't be CREATE transactions and require a destination address"
128 ));
129 }
130
131 sig = constructor_sig;
132 args = constructor_args;
133 Some(code)
134 } else {
135 None
136 };
137
138 let config = send_tx.eth.load_config()?;
139 let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
140
141 if let Some(interval) = send_tx.poll_interval {
142 provider.client().set_poll_interval(Duration::from_secs(interval))
143 }
144
145 let builder = CastTxBuilder::new(&provider, tx, &config)
146 .await?
147 .with_to(to)
148 .await?
149 .with_code_sig_and_args(code, sig, args)
150 .await?
151 .with_blob_data(blob_data)?;
152
153 if print_sponsor_hash {
155 let from = send_tx.eth.wallet.from.unwrap_or(config.sender);
156 let (tx, _) = builder.build(from).await?;
157 let hash = tx
158 .compute_sponsor_hash(from)
159 .ok_or_else(|| eyre!("This network does not support sponsored transactions"))?;
160 sh_println!("{hash:?}")?;
161 return Ok(());
162 }
163
164 let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
165
166 let browser = send_tx.browser.run::<N>().await?;
168
169 if unlocked && browser.is_none() {
174 if let Some(config_chain) = config.chain {
176 let current_chain_id = provider.get_chain_id().await?;
177 let config_chain_id = config_chain.id();
178 if config_chain_id != current_chain_id {
181 sh_warn!("Switching to chain {}", config_chain)?;
182 provider
183 .raw_request::<_, ()>(
184 "wallet_switchEthereumChain".into(),
185 [serde_json::json!({
186 "chainId": format!("0x{:x}", config_chain_id),
187 })],
188 )
189 .await?;
190 }
191 }
192
193 let (tx, _) = builder.build(config.sender).await?;
194
195 cast_send(
196 provider,
197 tx,
198 send_tx.cast_async,
199 send_tx.sync,
200 send_tx.confirmations,
201 timeout,
202 )
203 .await
204 } else if let Some(browser) = browser {
207 let (tx_request, _) = builder.build(browser.address()).await?;
208 let tx_hash = browser.send_transaction_via_browser(tx_request).await?;
209
210 let cast = CastTxSender::new(&provider);
211 cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await
212 } else {
217 let signer = send_tx.eth.wallet.signer().await?;
218 let from = signer.address();
219
220 tx::validate_from_address(send_tx.eth.wallet.from, from)?;
221
222 let (tx_request, _) = builder.build(&signer).await?;
223
224 let wallet = EthereumWallet::from(signer);
225 let provider = AlloyProviderBuilder::<_, _, N>::default()
226 .wallet(wallet)
227 .connect_provider(&provider);
228
229 cast_send(
230 provider,
231 tx_request,
232 send_tx.cast_async,
233 send_tx.sync,
234 send_tx.confirmations,
235 timeout,
236 )
237 .await
238 }
239 }
240}
241
242pub(crate) async fn cast_send<N: Network, P: Provider<N>>(
243 provider: P,
244 tx: N::TransactionRequest,
245 cast_async: bool,
246 sync: bool,
247 confs: u64,
248 timeout: u64,
249) -> Result<()>
250where
251 N::TransactionRequest: FoundryTransactionBuilder<N>,
252 N::ReceiptResponse: UIfmt + UIfmtReceiptExt,
253{
254 let cast = CastTxSender::new(provider);
255
256 if sync {
257 let receipt = cast.send_sync(tx).await?;
259 sh_println!("{receipt}")?;
260 } else {
261 let pending_tx = cast.send(tx).await?;
262 let tx_hash = *pending_tx.inner().tx_hash();
263 cast.print_tx_result(tx_hash, cast_async, confs, timeout).await?;
264 }
265
266 Ok(())
267}