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},
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(&config)?;
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 {
240 let receipt = cast
241 .receipt(
242 format!("{tx_hash:#x}"),
243 None,
244 send_tx.confirmations,
245 Some(timeout),
246 false,
247 )
248 .await?;
249 sh_println!("{receipt}")?;
250 }
251
252 return Ok(());
253 }
254
255 let (tx_request, _) = builder.build(&signer).await?;
256
257 let wallet = EthereumWallet::from(signer);
258 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
259 .wallet(wallet)
260 .connect_provider(&provider);
261
262 cast_send(
263 provider,
264 tx_request.into_inner().into(),
265 send_tx.cast_async,
266 send_tx.sync,
267 send_tx.confirmations,
268 timeout,
269 )
270 .await
271 }
272 }
273}
274
275pub(crate) async fn cast_send<P: Provider<AnyNetwork>>(
276 provider: P,
277 tx: WithOtherFields<TransactionRequest>,
278 cast_async: bool,
279 sync: bool,
280 confs: u64,
281 timeout: u64,
282) -> Result<()> {
283 let cast = CastTxSender::new(&provider);
284
285 if sync {
286 let receipt = cast.send_sync(tx).await?;
288 sh_println!("{receipt}")?;
289 } else {
290 let pending_tx = cast.send(tx).await?;
291 let tx_hash = pending_tx.inner().tx_hash();
292
293 if cast_async {
294 sh_println!("{tx_hash:#x}")?;
295 } else {
296 let receipt =
297 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
298 sh_println!("{receipt}")?;
299 }
300 }
301
302 Ok(())
303}