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};
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 args: Vec<String>,
34
35 #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")]
37 cast_async: bool,
38
39 #[arg(long, default_value = "1")]
41 confirmations: u64,
42
43 #[command(subcommand)]
44 command: Option<SendTxSubcommands>,
45
46 #[arg(long, requires = "from")]
48 unlocked: bool,
49
50 #[arg(long, env = "ETH_TIMEOUT")]
52 pub timeout: Option<u64>,
53
54 #[command(flatten)]
55 tx: TransactionOpts,
56
57 #[command(flatten)]
58 eth: EthereumOpts,
59
60 #[arg(
62 long,
63 value_name = "BLOB_DATA_PATH",
64 conflicts_with = "legacy",
65 requires = "blob",
66 help_heading = "Transaction options"
67 )]
68 path: Option<PathBuf>,
69}
70
71#[derive(Debug, Parser)]
72pub enum SendTxSubcommands {
73 #[command(name = "--create")]
75 Create {
76 code: String,
78
79 sig: Option<String>,
81
82 args: Vec<String>,
84 },
85}
86
87impl SendTxArgs {
88 pub async fn run(self) -> eyre::Result<()> {
89 let Self {
90 eth,
91 to,
92 mut sig,
93 cast_async,
94 mut args,
95 tx,
96 confirmations,
97 command,
98 unlocked,
99 path,
100 timeout,
101 } = self;
102
103 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
104
105 let code = if let Some(SendTxSubcommands::Create {
106 code,
107 sig: constructor_sig,
108 args: constructor_args,
109 }) = command
110 {
111 if to.is_none() && tx.auth.is_some() {
114 return Err(eyre!(
115 "EIP-7702 transactions can't be CREATE transactions and require a destination address"
116 ));
117 }
118 if to.is_none() && blob_data.is_some() {
121 return Err(eyre!(
122 "EIP-4844 transactions can't be CREATE transactions and require a destination address"
123 ));
124 }
125
126 sig = constructor_sig;
127 args = constructor_args;
128 Some(code)
129 } else {
130 None
131 };
132
133 let config = eth.load_config()?;
134 let provider = utils::get_provider(&config)?;
135
136 let builder = CastTxBuilder::new(&provider, tx, &config)
137 .await?
138 .with_to(to)
139 .await?
140 .with_code_sig_and_args(code, sig, args)
141 .await?
142 .with_blob_data(blob_data)?;
143
144 let timeout = timeout.unwrap_or(config.transaction_timeout);
145
146 if unlocked {
151 if let Some(config_chain) = config.chain {
153 let current_chain_id = provider.get_chain_id().await?;
154 let config_chain_id = config_chain.id();
155 if config_chain_id != current_chain_id {
158 sh_warn!("Switching to chain {}", config_chain)?;
159 provider
160 .raw_request::<_, ()>(
161 "wallet_switchEthereumChain".into(),
162 [serde_json::json!({
163 "chainId": format!("0x{:x}", config_chain_id),
164 })],
165 )
166 .await?;
167 }
168 }
169
170 let (tx, _) = builder.build(config.sender).await?;
171
172 cast_send(provider, tx, cast_async, confirmations, timeout).await
173 } else {
178 let signer = eth.wallet.signer().await?;
180 let from = signer.address();
181
182 tx::validate_from_address(eth.wallet.from, from)?;
183
184 let (tx, _) = builder.build(&signer).await?;
185
186 let wallet = EthereumWallet::from(signer);
187 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
188 .wallet(wallet)
189 .connect_provider(&provider);
190
191 cast_send(provider, tx, cast_async, confirmations, timeout).await
192 }
193 }
194}
195
196async fn cast_send<P: Provider<AnyNetwork>>(
197 provider: P,
198 tx: WithOtherFields<TransactionRequest>,
199 cast_async: bool,
200 confs: u64,
201 timeout: u64,
202) -> Result<()> {
203 let cast = Cast::new(provider);
204 let pending_tx = cast.send(tx).await?;
205
206 let tx_hash = pending_tx.inner().tx_hash();
207
208 if cast_async {
209 sh_println!("{tx_hash:#x}")?;
210 } else {
211 let receipt =
212 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
213 sh_println!("{receipt}")?;
214 }
215
216 Ok(())
217}