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