1use crate::{
8 call_spec::CallSpec,
9 cmd::{
10 auth::{confirm_and_build, confirm_and_build_with_tempo_wallet},
11 send::{SendOptions, cast_send, cast_send_with_tempo_wallet},
12 },
13 tempo,
14 tx::{self, CastTxBuilder, InitState, InputState, SendTxOpts, apply_poll_interval},
15};
16use alloy_network::EthereumWallet;
17use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder};
18use clap::Parser;
19use eyre::Result;
20use foundry_cli::{
21 opts::TransactionOpts,
22 utils::{self, resolve_lane},
23};
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)]
49 pub force: bool,
50
51 #[arg(long, requires = "from")]
53 pub unlocked: bool,
54}
55
56impl BatchSendArgs {
57 pub async fn run(self) -> Result<()> {
58 let Self { calls, send_tx, mut tx, force, unlocked } = self;
59 if tx.tempo.session_id()?.is_some() && unlocked {
62 eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --unlocked");
63 }
64 tempo::ensure_session_not_browser(&tx.tempo, send_tx.browser.browser)?;
65
66 let expires_at = tx.tempo.resolve_expires();
67
68 let (config, provider) = tempo::tempo_provider(&send_tx.eth)?;
69 let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
70 let lane = resolved_lane.as_ref();
71
72 apply_poll_interval(&provider, send_tx.poll_interval);
73
74 let chain = utils::get_chain(config.chain, &provider).await?;
75 let (signer, tempo_access_key) =
76 tempo::resolve_session_or_wallet_signer(&tx.tempo, &send_tx.eth.wallet, chain.id())
77 .await?;
78
79 if let Some(access_key) = &tempo_access_key {
81 tx.tempo.key_id = Some(access_key.key_id()?);
82 }
83
84 let builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
85 let builder = with_batch_calls(&calls, builder, &provider).await?;
86 tempo::print_expires(expires_at)?;
87
88 let send_opts =
89 SendOptions::new(&send_tx, &config).resolving_fee_token(Some(chain), &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}