1use crate::{
8 call_spec::CallSpec,
9 cmd::send::{cast_send, cast_send_with_access_key},
10 tempo,
11 tx::{self, CastTxBuilder, SendTxOpts},
12};
13use alloy_network::{EthereumWallet, TransactionBuilder};
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, maybe_print_resolved_lane, resolve_lane},
21};
22use foundry_common::provider::ProviderBuilder;
23use std::time::Duration;
24use tempo_alloy::TempoNetwork;
25
26#[derive(Debug, Parser)]
30pub struct BatchSendArgs {
31 #[arg(long = "call", value_name = "SPEC", required = true)]
39 pub calls: Vec<String>,
40
41 #[command(flatten)]
42 pub send_tx: SendTxOpts,
43
44 #[command(flatten)]
45 pub tx: TransactionOpts,
46
47 #[arg(long, requires = "from")]
49 pub unlocked: bool,
50}
51
52impl BatchSendArgs {
53 pub async fn run(self) -> Result<()> {
54 let Self { calls, send_tx, mut tx, unlocked } = self;
55 let expires_at = tx.tempo.resolve_expires();
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 let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
67
68 if let Some(interval) = send_tx.poll_interval {
69 provider.client().set_poll_interval(Duration::from_secs(interval))
70 }
71
72 let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?;
74
75 let call_specs: Vec<CallSpec> =
77 calls.iter().map(|s| CallSpec::parse(s)).collect::<Result<Vec<_>>>()?;
78
79 let chain = utils::get_chain(config.chain, &provider).await?;
81 let etherscan_config = config.get_etherscan_config_with_chain(Some(chain)).ok().flatten();
82 let etherscan_api_key = etherscan_config.as_ref().map(|c| c.key.clone());
83 let etherscan_api_url = etherscan_config.map(|c| c.api_url);
84
85 let mut tempo_calls = Vec::with_capacity(call_specs.len());
87 for (i, spec) in call_specs.iter().enumerate() {
88 tempo_calls.push(
89 spec.resolve(
90 i,
91 chain,
92 &provider,
93 etherscan_api_key.as_deref(),
94 etherscan_api_url.as_deref(),
95 )
96 .await?,
97 );
98 }
99
100 sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?;
101 tempo::print_expires(expires_at)?;
102
103 if let Some(ref access_key) = tempo_access_key {
105 tx.tempo.key_id = Some(access_key.key_address);
106 }
107
108 let mut builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
110
111 builder.tx.calls = tempo_calls;
113
114 let first_call_to = call_specs.first().map(|s| s.to);
117 let builder = builder.with_to(first_call_to.map(Into::into)).await?;
118
119 let builder = builder.with_code_sig_and_args(None, None, vec![]).await?;
121
122 let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
123
124 if unlocked {
125 let (tx, _) = builder.build(config.sender).await?;
126 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
127 cast_send(
128 provider,
129 tx,
130 send_tx.cast_async,
131 send_tx.sync,
132 send_tx.confirmations,
133 timeout,
134 )
135 .await
136 } else {
137 let signer = match signer {
138 Some(s) => s,
139 None => send_tx.eth.wallet.signer().await?,
140 };
141
142 if let Some(ref access_key) = tempo_access_key {
143 let (tx_request, _) =
144 builder.build_with_access_key(access_key.wallet_address, access_key).await?;
145 maybe_print_resolved_lane(
146 resolved_lane.as_ref(),
147 tx_request.nonce().unwrap_or_default(),
148 )?;
149 cast_send_with_access_key(
150 &provider,
151 tx_request,
152 &signer,
153 access_key,
154 send_tx.cast_async,
155 send_tx.confirmations,
156 timeout,
157 )
158 .await?;
159 } else {
160 tx::validate_from_address(send_tx.eth.wallet.from, Signer::address(&signer))?;
161 let (tx_request, _) = builder.build(&signer).await?;
162 maybe_print_resolved_lane(
163 resolved_lane.as_ref(),
164 tx_request.nonce().unwrap_or_default(),
165 )?;
166 let wallet = EthereumWallet::from(signer);
167 let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default()
168 .wallet(wallet)
169 .connect_provider(&provider);
170
171 cast_send(
172 provider,
173 tx_request,
174 send_tx.cast_async,
175 send_tx.sync,
176 send_tx.confirmations,
177 timeout,
178 )
179 .await?;
180 }
181
182 Ok(())
183 }
184 }
185}