1use crate::{
8 call_spec::CallSpec,
9 cmd::send::cast_send,
10 tx::{self, CastTxBuilder, CastTxSender, SendTxOpts},
11};
12use alloy_network::EthereumWallet;
13use alloy_primitives::Bytes;
14use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder};
15use alloy_signer::Signer;
16use clap::Parser;
17use eyre::{Result, eyre};
18use foundry_cli::{
19 opts::TransactionOpts,
20 utils::{self, LoadConfig, parse_function_args},
21};
22use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder};
23use std::time::Duration;
24use tempo_alloy::TempoNetwork;
25use tempo_primitives::transaction::Call;
26
27#[derive(Debug, Parser)]
31pub struct BatchSendArgs {
32 #[arg(long = "call", value_name = "SPEC", required = true)]
40 pub calls: Vec<String>,
41
42 #[command(flatten)]
43 pub send_tx: SendTxOpts,
44
45 #[command(flatten)]
46 pub tx: TransactionOpts,
47
48 #[arg(long, requires = "from")]
50 pub unlocked: bool,
51}
52
53impl BatchSendArgs {
54 pub async fn run(self) -> Result<()> {
55 let Self { calls, send_tx, tx, unlocked } = self;
56
57 if calls.is_empty() {
58 return Err(eyre!("No calls specified. Use --call to specify at least one call."));
59 }
60
61 let config = send_tx.eth.load_config()?;
62 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
63
64 if let Some(interval) = send_tx.poll_interval {
65 provider.client().set_poll_interval(Duration::from_secs(interval))
66 }
67
68 let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?;
70
71 let call_specs: Vec<CallSpec> =
73 calls.iter().map(|s| CallSpec::parse(s)).collect::<Result<Vec<_>>>()?;
74
75 let chain = utils::get_chain(config.chain, &provider).await?;
77 let etherscan_api_key = config.get_etherscan_api_key(Some(chain));
78
79 let mut tempo_calls = Vec::with_capacity(call_specs.len());
81 for (i, spec) in call_specs.iter().enumerate() {
82 let input = if let Some(data) = &spec.data {
83 data.clone()
84 } else if let Some(sig) = &spec.sig {
85 let (encoded, _) = parse_function_args(
86 sig,
87 spec.args.clone(),
88 Some(spec.to),
89 chain,
90 &provider,
91 etherscan_api_key.as_deref(),
92 )
93 .await
94 .map_err(|e| eyre!("Failed to encode call {}: {}", i + 1, e))?;
95 Bytes::from(encoded)
96 } else {
97 Bytes::new()
98 };
99
100 tempo_calls.push(Call { to: spec.to.into(), value: spec.value, input });
101 }
102
103 sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?;
104
105 let mut builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
107
108 if let Some(ref access_key) = tempo_access_key {
110 builder.tx.set_key_id(access_key.key_address);
111 }
112
113 builder.tx.calls = tempo_calls;
115
116 let first_call_to = call_specs.first().map(|s| s.to);
119 let builder = builder.with_to(first_call_to.map(Into::into)).await?;
120
121 let builder = builder.with_code_sig_and_args(None, None, vec![]).await?;
123
124 let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
125
126 if unlocked {
127 let (tx, _) = builder.build(config.sender).await?;
128 cast_send(
129 provider,
130 tx,
131 send_tx.cast_async,
132 send_tx.sync,
133 send_tx.confirmations,
134 timeout,
135 )
136 .await
137 } else {
138 let signer = match signer {
139 Some(s) => s,
140 None => send_tx.eth.wallet.signer().await?,
141 };
142 let from = if let Some(ref access_key) = tempo_access_key {
143 access_key.wallet_address
144 } else {
145 Signer::address(&signer)
146 };
147
148 if tempo_access_key.is_none() {
149 tx::validate_from_address(send_tx.eth.wallet.from, from)?;
150 }
151
152 let (tx_request, _) = if tempo_access_key.is_some() {
153 builder.build(from).await?
154 } else {
155 builder.build(&signer).await?
156 };
157
158 if let Some(ref access_key) = tempo_access_key {
159 let raw_tx = tx_request
160 .sign_with_access_key(
161 &provider,
162 &signer,
163 access_key.wallet_address,
164 access_key.key_address,
165 access_key.key_authorization.as_ref(),
166 )
167 .await?;
168
169 let cast = CastTxSender::new(&provider);
170 let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash();
171 cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout)
172 .await?;
173 } else {
174 let wallet = EthereumWallet::from(signer);
175 let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default()
176 .wallet(wallet)
177 .connect_provider(&provider);
178
179 cast_send(
180 provider,
181 tx_request,
182 send_tx.cast_async,
183 send_tx.sync,
184 send_tx.confirmations,
185 timeout,
186 )
187 .await?;
188 }
189
190 Ok(())
191 }
192 }
193}