1use crate::{
7 call_spec::CallSpec,
8 tempo,
9 tx::{self, CastTxBuilder},
10};
11use alloy_consensus::SignableTransaction;
12use alloy_eips::eip2718::Encodable2718;
13use alloy_network::{EthereumWallet, NetworkTransactionBuilder, TransactionBuilder};
14use alloy_primitives::Address;
15use alloy_provider::Provider;
16use alloy_signer::Signer;
17use clap::Parser;
18use eyre::{Result, eyre};
19use foundry_cli::{
20 opts::{EthereumOpts, TransactionOpts},
21 utils::{self, LoadConfig, maybe_print_resolved_lane, resolve_lane},
22};
23use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder};
24use tempo_alloy::TempoNetwork;
25
26#[derive(Debug, Parser)]
30pub struct BatchMakeTxArgs {
31 #[arg(long = "call", value_name = "SPEC", required = true)]
38 pub calls: Vec<String>,
39
40 #[command(flatten)]
41 pub tx: TransactionOpts,
42
43 #[command(flatten)]
44 pub eth: EthereumOpts,
45
46 #[arg(long)]
48 pub raw_unsigned: bool,
49
50 #[arg(long, requires = "from", conflicts_with = "raw_unsigned")]
52 pub ethsign: bool,
53}
54
55impl BatchMakeTxArgs {
56 pub async fn run(self) -> Result<()> {
57 let Self { calls, mut tx, eth, raw_unsigned, ethsign } = self;
58 let has_nonce = tx.nonce.is_some();
59 let expires_at = tx.tempo.resolve_expires();
60
61 if calls.is_empty() {
62 return Err(eyre!("No calls specified. Use --call to specify at least one call."));
63 }
64
65 let config = eth.load_config()?;
66 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
67
68 let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
71
72 let (signer, tempo_access_key) = 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());
86 for (i, spec) in call_specs.iter().enumerate() {
87 tempo_calls.push(
88 spec.resolve(
89 i,
90 chain,
91 &provider,
92 etherscan_api_key.as_deref(),
93 etherscan_api_url.as_deref(),
94 )
95 .await?,
96 );
97 }
98
99 sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?;
100 tempo::print_expires(expires_at)?;
101
102 if let Some(ref access_key) = tempo_access_key {
104 tx.tempo.key_id = Some(access_key.key_address);
105 }
106
107 let mut builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
109
110 builder.tx.calls = tempo_calls;
112
113 let first_call_to = call_specs.first().map(|s| s.to);
115 let builder = builder.with_to(first_call_to.map(Into::into)).await?;
116 let tx_builder = builder.with_code_sig_and_args(None, None, vec![]).await?;
117
118 if raw_unsigned {
119 if eth.wallet.from.is_none() && !has_nonce {
120 eyre::bail!(
121 "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce"
122 );
123 }
124
125 let from = eth.wallet.from.unwrap_or(Address::ZERO);
126 let (tx, _) = tx_builder.build(from).await?;
127 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
128 let raw_tx =
129 alloy_primitives::hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing());
130 sh_println!("{raw_tx}")?;
131 return Ok(());
132 }
133
134 if ethsign {
135 let (tx, _) = tx_builder.build(config.sender).await?;
136 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
137 let signed_tx = provider.sign_transaction(tx).await?;
138 sh_println!("{signed_tx}")?;
139 return Ok(());
140 }
141
142 let signer = match signer {
144 Some(s) => s,
145 None => eth.wallet.signer().await?,
146 };
147
148 let signed_tx = if let Some(ref access_key) = tempo_access_key {
149 let (tx, _) =
150 tx_builder.build_with_access_key(access_key.wallet_address, access_key).await?;
151 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
152 let raw_tx = tx
153 .sign_with_access_key(
154 &provider,
155 &signer,
156 access_key.wallet_address,
157 access_key.key_address,
158 access_key.key_authorization.as_ref(),
159 )
160 .await?;
161 alloy_primitives::hex::encode(raw_tx)
162 } else {
163 tx::validate_from_address(eth.wallet.from, Signer::address(&signer))?;
164 let (tx, _) = tx_builder.build(&signer).await?;
165 maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
166 let envelope = tx.build(&EthereumWallet::new(signer)).await?;
167 alloy_primitives::hex::encode(envelope.encoded_2718())
168 };
169
170 sh_println!("0x{signed_tx}")?;
171
172 Ok(())
173 }
174}