1use crate::{
2 tx::{self, CastTxBuilder},
3 Cast,
4};
5use alloy_network::{AnyNetwork, EthereumWallet};
6use alloy_provider::{Provider, ProviderBuilder};
7use alloy_rpc_types::TransactionRequest;
8use alloy_serde::WithOtherFields;
9use alloy_signer::Signer;
10use clap::Parser;
11use eyre::Result;
12use foundry_cli::{
13 opts::{EthereumOpts, TransactionOpts},
14 utils,
15 utils::LoadConfig,
16};
17use foundry_common::ens::NameOrAddress;
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 #[expect(dependency_on_unit_never_type_fallback)]
89 pub async fn run(self) -> eyre::Result<()> {
90 let Self {
91 eth,
92 to,
93 mut sig,
94 cast_async,
95 mut args,
96 tx,
97 confirmations,
98 command,
99 unlocked,
100 path,
101 timeout,
102 } = self;
103
104 let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
105
106 let code = if let Some(SendTxSubcommands::Create {
107 code,
108 sig: constructor_sig,
109 args: constructor_args,
110 }) = command
111 {
112 sig = constructor_sig;
113 args = constructor_args;
114 Some(code)
115 } else {
116 None
117 };
118
119 let config = eth.load_config()?;
120 let provider = utils::get_provider(&config)?;
121
122 let builder = CastTxBuilder::new(&provider, tx, &config)
123 .await?
124 .with_to(to)
125 .await?
126 .with_code_sig_and_args(code, sig, args)
127 .await?
128 .with_blob_data(blob_data)?;
129
130 let timeout = timeout.unwrap_or(config.transaction_timeout);
131
132 if unlocked {
137 if let Some(config_chain) = config.chain {
139 let current_chain_id = provider.get_chain_id().await?;
140 let config_chain_id = config_chain.id();
141 if config_chain_id != current_chain_id {
144 sh_warn!("Switching to chain {}", config_chain)?;
145 provider
146 .raw_request(
147 "wallet_switchEthereumChain".into(),
148 [serde_json::json!({
149 "chainId": format!("0x{:x}", config_chain_id),
150 })],
151 )
152 .await?;
153 }
154 }
155
156 let (tx, _) = builder.build(config.sender).await?;
157
158 cast_send(provider, tx, cast_async, confirmations, timeout).await
159 } else {
164 let signer = eth.wallet.signer().await?;
166 let from = signer.address();
167
168 tx::validate_from_address(eth.wallet.from, from)?;
169
170 let (tx, _) = builder.build(&signer).await?;
171
172 let wallet = EthereumWallet::from(signer);
173 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
174 .wallet(wallet)
175 .on_provider(&provider);
176
177 cast_send(provider, tx, cast_async, confirmations, timeout).await
178 }
179 }
180}
181
182async fn cast_send<P: Provider<AnyNetwork>>(
183 provider: P,
184 tx: WithOtherFields<TransactionRequest>,
185 cast_async: bool,
186 confs: u64,
187 timeout: u64,
188) -> Result<()> {
189 let cast = Cast::new(provider);
190 let pending_tx = cast.send(tx).await?;
191
192 let tx_hash = pending_tx.inner().tx_hash();
193
194 if cast_async {
195 sh_println!("{tx_hash:#x}")?;
196 } else {
197 let receipt =
198 cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
199 sh_println!("{receipt}")?;
200 }
201
202 Ok(())
203}