1use std::{path::PathBuf, str::FromStr, time::Duration};
2
3use alloy_eips::Encodable2718;
4use alloy_ens::NameOrAddress;
5use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder};
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().into(),
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 =
199 browser_signer.send_transaction_via_browser(tx_request.into_inner()).await?;
200
201 if send_tx.cast_async {
202 sh_println!("{tx_hash:#x}")?;
203 } else {
204 let receipt = CastTxSender::new(&provider)
205 .receipt(
206 format!("{tx_hash:#x}"),
207 None,
208 send_tx.confirmations,
209 Some(timeout),
210 false,
211 )
212 .await?;
213 sh_println!("{receipt}")?;
214 }
215
216 return Ok(());
217 }
218
219 if is_tempo {
225 let (ftx, _) = builder.build(&signer).await?;
226
227 let signed_tx = ftx.build(&EthereumWallet::new(signer)).await?;
228
229 let mut raw_tx = Vec::with_capacity(signed_tx.encode_2718_len());
231 signed_tx.encode_2718(&mut raw_tx);
232
233 let cast = CastTxSender::new(&provider);
234 let pending_tx = cast.send_raw(&raw_tx).await?;
235 let tx_hash = pending_tx.inner().tx_hash();
236
237 if send_tx.cast_async {
238 sh_println!("{tx_hash:#x}")?;
239 } else if send_tx.sync {
240 let receipt = cast
242 .receipt(
243 format!("{tx_hash:#x}"),
244 None,
245 send_tx.confirmations,
246 Some(timeout),
247 false,
248 )
249 .await?;
250 sh_println!("{receipt}")?;
251 } else {
252 let receipt = cast
253 .receipt(
254 format!("{tx_hash:#x}"),
255 None,
256 send_tx.confirmations,
257 Some(timeout),
258 false,
259 )
260 .await?;
261 sh_println!("{receipt}")?;
262 }
263
264 return Ok(());
265 }
266
267 let (tx_request, _) = builder.build(&signer).await?;
268
269 let wallet = EthereumWallet::from(signer);
270 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
271 .wallet(wallet)
272 .connect_provider(&provider);
273
274 cast_send(
275 provider,
276 tx_request.into_inner().into(),
277 send_tx.cast_async,
278 send_tx.sync,
279 send_tx.confirmations,
280 timeout,
281 )
282 .await
283 }
284 }
285}
286
287pub(crate) async fn cast_send<P: Provider<AnyNetwork>>(
288 provider: P,
289 tx: WithOtherFields<TransactionRequest>,
290 cast_async: bool,
291 sync: bool,
292 confs: u64,
293 timeout: u64,
294) -> Result<()> {
295 let cast = CastTxSender::new(&provider);
296
297 if sync {
298 let receipt = cast.send_sync(tx).await?;
300 sh_println!("{receipt}")?;
301 } else {
302 let pending_tx = cast.send(tx).await?;
303 let tx_hash = pending_tx.inner().tx_hash();
304
305 if cast_async {
306 sh_println!("{tx_hash:#x}")?;
307 } else {
308 let receipt =
309 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
310 sh_println!("{receipt}")?;
311 }
312 }
313
314 Ok(())
315}