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