1use std::{path::PathBuf, str::FromStr, time::Duration};
2
3use alloy_eips::Encodable2718;
4use alloy_ens::NameOrAddress;
5use alloy_network::{AnyNetwork, EthereumWallet, NetworkWallet};
6use alloy_provider::{Provider, ProviderBuilder};
7use alloy_rpc_types::TransactionRequest;
8use alloy_serde::WithOtherFields;
9use alloy_signer::Signer;
10use clap::Parser;
11use eyre::{Result, eyre};
12use foundry_cli::{
13 opts::TransactionOpts,
14 utils::{LoadConfig, get_provider_with_curl},
15};
16use foundry_wallets::WalletSigner;
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) -> eyre::Result<()> {
86 let Self { to, mut sig, mut args, data, send_tx, tx, command, unlocked, path } = self;
87
88 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
89
90 if let Some(data) = data {
91 sig = Some(data);
92 }
93
94 let code = if let Some(SendTxSubcommands::Create {
95 code,
96 sig: constructor_sig,
97 args: constructor_args,
98 }) = command
99 {
100 if to.is_none() && !tx.auth.is_empty() {
103 return Err(eyre!(
104 "EIP-7702 transactions can't be CREATE transactions and require a destination address"
105 ));
106 }
107 if to.is_none() && blob_data.is_some() {
110 return Err(eyre!(
111 "EIP-4844 transactions can't be CREATE transactions and require a destination address"
112 ));
113 }
114
115 sig = constructor_sig;
116 args = constructor_args;
117 Some(code)
118 } else {
119 None
120 };
121
122 let config = send_tx.eth.load_config()?;
123 let provider = get_provider_with_curl(&config, send_tx.eth.rpc.curl)?;
124
125 if let Some(interval) = send_tx.poll_interval {
126 provider.client().set_poll_interval(Duration::from_secs(interval))
127 }
128
129 let builder = CastTxBuilder::new(&provider, tx, &config)
130 .await?
131 .with_to(to)
132 .await?
133 .with_code_sig_and_args(code, sig, args)
134 .await?
135 .with_blob_data(blob_data)?;
136
137 let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
138
139 let is_tempo = builder.is_tempo();
141
142 if is_tempo && send_tx.eth.wallet.browser {
144 return Err(eyre!("Tempo transactions are not supported with browser wallets."));
145 }
146
147 if unlocked && !send_tx.eth.wallet.browser {
152 if let Some(config_chain) = config.chain {
154 let current_chain_id = provider.get_chain_id().await?;
155 let config_chain_id = config_chain.id();
156 if config_chain_id != current_chain_id {
159 sh_warn!("Switching to chain {}", config_chain)?;
160 provider
161 .raw_request::<_, ()>(
162 "wallet_switchEthereumChain".into(),
163 [serde_json::json!({
164 "chainId": format!("0x{:x}", config_chain_id),
165 })],
166 )
167 .await?;
168 }
169 }
170
171 let (tx, _) = builder.build(config.sender).await?;
172
173 cast_send(
174 provider,
175 tx.into_inner(),
176 send_tx.cast_async,
177 send_tx.sync,
178 send_tx.confirmations,
179 timeout,
180 )
181 .await
182 } else {
187 let signer = send_tx.eth.wallet.signer().await?;
189 let from = signer.address();
190
191 tx::validate_from_address(send_tx.eth.wallet.from, from)?;
192
193 if send_tx.eth.wallet.browser
195 && let WalletSigner::Browser(ref browser_signer) = signer
196 {
197 let (tx_request, _) = builder.build(from).await?;
198 let tx_hash = browser_signer
199 .send_transaction_via_browser(tx_request.into_inner().inner)
200 .await?;
201
202 if send_tx.cast_async {
203 sh_println!("{tx_hash:#x}")?;
204 } else {
205 let receipt = CastTxSender::new(&provider)
206 .receipt(
207 format!("{tx_hash:#x}"),
208 None,
209 send_tx.confirmations,
210 Some(timeout),
211 false,
212 )
213 .await?;
214 sh_println!("{receipt}")?;
215 }
216
217 return Ok(());
218 }
219
220 if is_tempo {
226 let (ftx, _) = builder.build(&signer).await?;
227
228 let signed_tx = signer.sign_request(ftx).await?;
230
231 let mut raw_tx = Vec::with_capacity(signed_tx.encode_2718_len());
233 signed_tx.encode_2718(&mut raw_tx);
234
235 let cast = CastTxSender::new(&provider);
236 let pending_tx = cast.send_raw(&raw_tx).await?;
237 let tx_hash = pending_tx.inner().tx_hash();
238
239 if send_tx.cast_async {
240 sh_println!("{tx_hash:#x}")?;
241 } else if send_tx.sync {
242 let receipt = cast
244 .receipt(
245 format!("{tx_hash:#x}"),
246 None,
247 send_tx.confirmations,
248 Some(timeout),
249 false,
250 )
251 .await?;
252 sh_println!("{receipt}")?;
253 } else {
254 let receipt = cast
255 .receipt(
256 format!("{tx_hash:#x}"),
257 None,
258 send_tx.confirmations,
259 Some(timeout),
260 false,
261 )
262 .await?;
263 sh_println!("{receipt}")?;
264 }
265
266 return Ok(());
267 }
268
269 let (tx_request, _) = builder.build(&signer).await?;
270
271 let wallet = EthereumWallet::from(signer);
272 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
273 .wallet(wallet)
274 .connect_provider(&provider);
275
276 cast_send(
277 provider,
278 tx_request.into_inner(),
279 send_tx.cast_async,
280 send_tx.sync,
281 send_tx.confirmations,
282 timeout,
283 )
284 .await
285 }
286 }
287}
288
289pub(crate) async fn cast_send<P: Provider<AnyNetwork>>(
290 provider: P,
291 tx: WithOtherFields<TransactionRequest>,
292 cast_async: bool,
293 sync: bool,
294 confs: u64,
295 timeout: u64,
296) -> Result<()> {
297 let cast = CastTxSender::new(&provider);
298
299 if sync {
300 let receipt = cast.send_sync(tx).await?;
302 sh_println!("{receipt}")?;
303 } else {
304 let pending_tx = cast.send(tx).await?;
305 let tx_hash = pending_tx.inner().tx_hash();
306
307 if cast_async {
308 sh_println!("{tx_hash:#x}")?;
309 } else {
310 let receipt =
311 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
312 sh_println!("{receipt}")?;
313 }
314 }
315
316 Ok(())
317}