1use std::{path::PathBuf, str::FromStr, time::Duration};
2
3use alloy_ens::NameOrAddress;
4use alloy_network::{AnyNetwork, EthereumWallet};
5use alloy_provider::{Provider, ProviderBuilder};
6use alloy_rpc_types::TransactionRequest;
7use alloy_serde::WithOtherFields;
8use alloy_signer::Signer;
9use clap::Parser;
10use eyre::{Result, eyre};
11use foundry_cli::{opts::TransactionOpts, utils, utils::LoadConfig};
12use foundry_wallets::WalletSigner;
13
14use crate::tx::{self, CastTxBuilder, CastTxSender, SendTxOpts};
15
16#[derive(Debug, Parser)]
18pub struct SendTxArgs {
19 #[arg(value_parser = NameOrAddress::from_str)]
23 to: Option<NameOrAddress>,
24
25 sig: Option<String>,
27
28 #[arg(allow_negative_numbers = true)]
30 args: Vec<String>,
31
32 #[arg(
34 long,
35 conflicts_with_all = &["sig", "args"]
36 )]
37 data: Option<String>,
38
39 #[command(flatten)]
40 send_tx: SendTxOpts,
41
42 #[command(subcommand)]
43 command: Option<SendTxSubcommands>,
44
45 #[arg(long, requires = "from")]
47 unlocked: bool,
48
49 #[command(flatten)]
50 tx: TransactionOpts,
51
52 #[arg(
54 long,
55 value_name = "BLOB_DATA_PATH",
56 conflicts_with = "legacy",
57 requires = "blob",
58 help_heading = "Transaction options"
59 )]
60 path: Option<PathBuf>,
61}
62
63#[derive(Debug, Parser)]
64pub enum SendTxSubcommands {
65 #[command(name = "--create")]
67 Create {
68 code: String,
70
71 sig: Option<String>,
73
74 #[arg(allow_negative_numbers = true)]
76 args: Vec<String>,
77 },
78}
79
80impl SendTxArgs {
81 pub async fn run(self) -> eyre::Result<()> {
82 let Self { to, mut sig, mut args, data, send_tx, tx, command, unlocked, path } = self;
83
84 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
85
86 if let Some(data) = data {
87 sig = Some(data);
88 }
89
90 let code = if let Some(SendTxSubcommands::Create {
91 code,
92 sig: constructor_sig,
93 args: constructor_args,
94 }) = command
95 {
96 if to.is_none() && !tx.auth.is_empty() {
99 return Err(eyre!(
100 "EIP-7702 transactions can't be CREATE transactions and require a destination address"
101 ));
102 }
103 if to.is_none() && blob_data.is_some() {
106 return Err(eyre!(
107 "EIP-4844 transactions can't be CREATE transactions and require a destination address"
108 ));
109 }
110
111 sig = constructor_sig;
112 args = constructor_args;
113 Some(code)
114 } else {
115 None
116 };
117
118 let config = send_tx.eth.load_config()?;
119 let provider = utils::get_provider(&config)?;
120
121 if let Some(interval) = send_tx.poll_interval {
122 provider.client().set_poll_interval(Duration::from_secs(interval))
123 }
124
125 let builder = CastTxBuilder::new(&provider, tx, &config)
126 .await?
127 .with_to(to)
128 .await?
129 .with_code_sig_and_args(code, sig, args)
130 .await?
131 .with_blob_data(blob_data)?;
132
133 let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
134
135 if unlocked && !send_tx.eth.wallet.browser {
140 if let Some(config_chain) = config.chain {
142 let current_chain_id = provider.get_chain_id().await?;
143 let config_chain_id = config_chain.id();
144 if config_chain_id != current_chain_id {
147 sh_warn!("Switching to chain {}", config_chain)?;
148 provider
149 .raw_request::<_, ()>(
150 "wallet_switchEthereumChain".into(),
151 [serde_json::json!({
152 "chainId": format!("0x{:x}", config_chain_id),
153 })],
154 )
155 .await?;
156 }
157 }
158
159 let (tx, _) = builder.build(config.sender).await?;
160
161 cast_send(
162 provider,
163 tx,
164 send_tx.cast_async,
165 send_tx.sync,
166 send_tx.confirmations,
167 timeout,
168 )
169 .await
170 } else {
175 let signer = send_tx.eth.wallet.signer().await?;
177 let from = signer.address();
178
179 tx::validate_from_address(send_tx.eth.wallet.from, from)?;
180
181 if send_tx.eth.wallet.browser
183 && let WalletSigner::Browser(ref browser_signer) = signer
184 {
185 let (tx_request, _) = builder.build(from).await?;
186 let tx_hash = browser_signer.send_transaction_via_browser(tx_request.inner).await?;
187
188 if send_tx.cast_async {
189 sh_println!("{tx_hash:#x}")?;
190 } else {
191 let receipt = CastTxSender::new(&provider)
192 .receipt(
193 format!("{tx_hash:#x}"),
194 None,
195 send_tx.confirmations,
196 Some(timeout),
197 false,
198 )
199 .await?;
200 sh_println!("{receipt}")?;
201 }
202
203 return Ok(());
204 }
205
206 let (tx_request, _) = builder.build(&signer).await?;
207
208 let wallet = EthereumWallet::from(signer);
209 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
210 .wallet(wallet)
211 .connect_provider(&provider);
212
213 cast_send(
214 provider,
215 tx_request,
216 send_tx.cast_async,
217 send_tx.sync,
218 send_tx.confirmations,
219 timeout,
220 )
221 .await
222 }
223 }
224}
225
226pub(crate) async fn cast_send<P: Provider<AnyNetwork>>(
227 provider: P,
228 tx: WithOtherFields<TransactionRequest>,
229 cast_async: bool,
230 sync: bool,
231 confs: u64,
232 timeout: u64,
233) -> Result<()> {
234 let cast = CastTxSender::new(&provider);
235
236 if sync {
237 let receipt = cast.send_sync(tx).await?;
239 sh_println!("{receipt}")?;
240 } else {
241 let pending_tx = cast.send(tx).await?;
242 let tx_hash = pending_tx.inner().tx_hash();
243
244 if cast_async {
245 sh_println!("{tx_hash:#x}")?;
246 } else {
247 let receipt =
248 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
249 sh_println!("{receipt}")?;
250 }
251 }
252
253 Ok(())
254}