1use crate::{
7 call_spec::CallSpec,
8 cmd::{
9 auth::{confirm_and_build, confirm_and_build_with_tempo_wallet},
10 send::{SendOptions, cast_send, cast_send_with_tempo_wallet},
11 },
12 tempo,
13 tx::{self, CastTxBuilder, InitState, InputState, SendTxOpts, apply_poll_interval},
14};
15use alloy_network::EthereumWallet;
16use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder};
17use clap::Parser;
18use eyre::Result;
19use foundry_cli::{
20 opts::TransactionOpts,
21 utils::{self, resolve_lane},
22};
23use tempo_alloy::TempoNetwork;
24
25#[derive(Debug, Parser)]
29pub struct BatchSendArgs {
30 #[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)]
50 pub force: bool,
51
52 #[arg(long, requires = "from")]
54 pub unlocked: bool,
55}
56
57impl BatchSendArgs {
58 pub async fn run(self) -> Result<()> {
59 let Self { calls, send_tx, mut tx, force, unlocked } = self;
60 if tx.tempo.session_id()?.is_some() && unlocked {
63 eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --unlocked");
64 }
65 tempo::ensure_session_not_browser(&tx.tempo, send_tx.browser.browser)?;
66
67 let expires_at = tx.tempo.resolve_expires();
68
69 let (config, provider) = tempo::tempo_provider(&send_tx.eth)?;
70 let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
71 let lane = resolved_lane.as_ref();
72
73 apply_poll_interval(&provider, send_tx.poll_interval);
74
75 let chain = utils::get_chain(config.chain, &provider).await?;
76 let (signer, tempo_access_key) =
77 tempo::resolve_session_or_wallet_signer(&tx.tempo, &send_tx.eth.wallet, chain.id())
78 .await?;
79
80 if let Some(access_key) = &tempo_access_key {
82 tx.tempo.key_id = Some(access_key.key_id()?);
83 }
84
85 let builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
86 let builder = with_batch_calls(&calls, builder, &provider).await?;
87 tempo::print_expires(expires_at)?;
88
89 let send_opts = SendOptions::new(&send_tx, &config).resolving_fee_token(true, &config);
90
91 if unlocked {
92 let Some(tx) = confirm_and_build(builder, config.sender, force, lane, false).await?
93 else {
94 return Ok(());
95 };
96 cast_send(provider, tx, &send_opts).await?;
97 } else if let Some(access_key) = &tempo_access_key {
98 let Some((tx_request, prepared)) =
99 confirm_and_build_with_tempo_wallet(builder, access_key, force, lane).await?
100 else {
101 return Ok(());
102 };
103 cast_send_with_tempo_wallet(&provider, tx_request, &prepared, &send_opts).await?;
104 } else {
105 let (signer, _) = tx::resolve_send_signer(signer, &send_tx.eth).await?;
106 let Some(tx_request) = confirm_and_build(builder, &signer, force, lane, false).await?
107 else {
108 return Ok(());
109 };
110 let provider = AlloyProviderBuilder::<_, _, TempoNetwork>::default()
111 .wallet(EthereumWallet::from(signer))
112 .connect_provider(&provider);
113 cast_send(provider, tx_request, &send_opts).await?;
114 }
115
116 Ok(())
117 }
118}
119
120pub(super) async fn with_batch_calls<P: Provider<TempoNetwork>>(
123 calls: &[String],
124 mut builder: CastTxBuilder<TempoNetwork, P, InitState>,
125 provider: &impl Provider<TempoNetwork>,
126) -> Result<CastTxBuilder<TempoNetwork, P, InputState>> {
127 let specs = calls.iter().map(|s| CallSpec::parse(s)).collect::<Result<Vec<_>>>()?;
128 let (etherscan_api_key, etherscan_api_url) = builder.etherscan_api();
129 let mut tempo_calls = Vec::with_capacity(specs.len());
130 for (i, spec) in specs.iter().enumerate() {
131 tempo_calls.push(
132 spec.resolve(i, builder.chain(), provider, etherscan_api_key, etherscan_api_url)
133 .await?,
134 );
135 }
136 sh_status!("Building batch transaction with {} call(s)...", tempo_calls.len())?;
137 builder.tx_mut().calls = tempo_calls;
138
139 builder
142 .with_to(specs.first().map(|spec| spec.to.into()))
143 .await?
144 .with_code_sig_and_args(None, None, vec![])
145 .await
146}