1use crate::{
2 Cast,
3 tx::{self, CastTxBuilder},
4};
5use alloy_ens::NameOrAddress;
6use alloy_network::{AnyNetwork, EthereumWallet};
7use alloy_provider::{Provider, ProviderBuilder};
8use alloy_rpc_types::TransactionRequest;
9use alloy_serde::WithOtherFields;
10use alloy_signer::Signer;
11use clap::Parser;
12use eyre::{Result, eyre};
13use foundry_cli::{
14 opts::{EthereumOpts, TransactionOpts},
15 utils,
16 utils::LoadConfig,
17};
18use std::{path::PathBuf, str::FromStr, time::Duration};
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(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")]
38 cast_async: bool,
39
40 #[arg(long, default_value = "1")]
42 confirmations: u64,
43
44 #[arg(long, alias = "poll-interval", env = "ETH_POLL_INTERVAL")]
46 poll_interval: Option<u64>,
47
48 #[command(subcommand)]
49 command: Option<SendTxSubcommands>,
50
51 #[arg(long, requires = "from")]
53 unlocked: bool,
54
55 #[arg(long, env = "ETH_TIMEOUT")]
57 pub timeout: Option<u64>,
58
59 #[command(flatten)]
60 tx: TransactionOpts,
61
62 #[command(flatten)]
63 eth: EthereumOpts,
64
65 #[arg(
67 long,
68 value_name = "BLOB_DATA_PATH",
69 conflicts_with = "legacy",
70 requires = "blob",
71 help_heading = "Transaction options"
72 )]
73 path: Option<PathBuf>,
74}
75
76#[derive(Debug, Parser)]
77pub enum SendTxSubcommands {
78 #[command(name = "--create")]
80 Create {
81 code: String,
83
84 sig: Option<String>,
86
87 #[arg(allow_negative_numbers = true)]
89 args: Vec<String>,
90 },
91}
92
93impl SendTxArgs {
94 pub async fn run(self) -> eyre::Result<()> {
95 let Self {
96 eth,
97 to,
98 mut sig,
99 cast_async,
100 mut args,
101 tx,
102 confirmations,
103 command,
104 unlocked,
105 path,
106 timeout,
107 poll_interval,
108 } = self;
109
110 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
111
112 let code = if let Some(SendTxSubcommands::Create {
113 code,
114 sig: constructor_sig,
115 args: constructor_args,
116 }) = command
117 {
118 if to.is_none() && tx.auth.is_some() {
121 return Err(eyre!(
122 "EIP-7702 transactions can't be CREATE transactions and require a destination address"
123 ));
124 }
125 if to.is_none() && blob_data.is_some() {
128 return Err(eyre!(
129 "EIP-4844 transactions can't be CREATE transactions and require a destination address"
130 ));
131 }
132
133 sig = constructor_sig;
134 args = constructor_args;
135 Some(code)
136 } else {
137 None
138 };
139
140 let config = eth.load_config()?;
141 let provider = utils::get_provider(&config)?;
142
143 if let Some(interval) = poll_interval {
144 provider.client().set_poll_interval(Duration::from_secs(interval))
145 }
146
147 let builder = CastTxBuilder::new(&provider, tx, &config)
148 .await?
149 .with_to(to)
150 .await?
151 .with_code_sig_and_args(code, sig, args)
152 .await?
153 .with_blob_data(blob_data)?;
154
155 let timeout = timeout.unwrap_or(config.transaction_timeout);
156
157 if unlocked {
162 if let Some(config_chain) = config.chain {
164 let current_chain_id = provider.get_chain_id().await?;
165 let config_chain_id = config_chain.id();
166 if config_chain_id != current_chain_id {
169 sh_warn!("Switching to chain {}", config_chain)?;
170 provider
171 .raw_request::<_, ()>(
172 "wallet_switchEthereumChain".into(),
173 [serde_json::json!({
174 "chainId": format!("0x{:x}", config_chain_id),
175 })],
176 )
177 .await?;
178 }
179 }
180
181 let (tx, _) = builder.build(config.sender).await?;
182
183 cast_send(provider, tx, cast_async, confirmations, timeout).await
184 } else {
189 let signer = eth.wallet.signer().await?;
191 let from = signer.address();
192
193 tx::validate_from_address(eth.wallet.from, from)?;
194
195 let (tx, _) = builder.build(&signer).await?;
196
197 let wallet = EthereumWallet::from(signer);
198 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
199 .wallet(wallet)
200 .connect_provider(&provider);
201
202 cast_send(provider, tx, cast_async, confirmations, timeout).await
203 }
204 }
205}
206
207async fn cast_send<P: Provider<AnyNetwork>>(
208 provider: P,
209 tx: WithOtherFields<TransactionRequest>,
210 cast_async: bool,
211 confs: u64,
212 timeout: u64,
213) -> Result<()> {
214 let cast = Cast::new(provider);
215 let pending_tx = cast.send(tx).await?;
216 let tx_hash = pending_tx.inner().tx_hash();
217
218 if cast_async {
219 sh_println!("{tx_hash:#x}")?;
220 } else {
221 let receipt =
222 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
223 sh_println!("{receipt}")?;
224 }
225
226 Ok(())
227}