1use crate::{
7 call_spec::CallSpec,
8 tx::{self, CastTxBuilder},
9};
10use alloy_consensus::SignableTransaction;
11use alloy_eips::eip2718::Encodable2718;
12use alloy_network::{EthereumWallet, TransactionBuilder};
13use alloy_primitives::{Address, Bytes};
14use alloy_provider::Provider;
15use alloy_signer::Signer;
16use clap::Parser;
17use eyre::{Result, eyre};
18use foundry_cli::{
19 opts::{EthereumOpts, TransactionOpts},
20 utils::{self, LoadConfig, parse_function_args},
21};
22use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder};
23use tempo_alloy::TempoNetwork;
24use tempo_primitives::transaction::Call;
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, tx, eth, raw_unsigned, ethsign } = self;
58 let has_nonce = tx.nonce.is_some();
59
60 if calls.is_empty() {
61 return Err(eyre!("No calls specified. Use --call to specify at least one call."));
62 }
63
64 let config = eth.load_config()?;
65 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
66
67 let (signer, tempo_access_key) = eth.wallet.maybe_signer().await?;
69
70 let call_specs: Vec<CallSpec> =
72 calls.iter().map(|s| CallSpec::parse(s)).collect::<Result<Vec<_>>>()?;
73
74 let chain = utils::get_chain(config.chain, &provider).await?;
76 let etherscan_api_key = config.get_etherscan_api_key(Some(chain));
77
78 let mut tempo_calls = Vec::with_capacity(call_specs.len());
80 for (i, spec) in call_specs.iter().enumerate() {
81 let input = if let Some(data) = &spec.data {
82 data.clone()
83 } else if let Some(sig) = &spec.sig {
84 let (encoded, _) = parse_function_args(
85 sig,
86 spec.args.clone(),
87 Some(spec.to),
88 chain,
89 &provider,
90 etherscan_api_key.as_deref(),
91 )
92 .await
93 .map_err(|e| eyre!("Failed to encode call {}: {}", i + 1, e))?;
94 Bytes::from(encoded)
95 } else {
96 Bytes::new()
97 };
98
99 tempo_calls.push(Call { to: spec.to.into(), value: spec.value, input });
100 }
101
102 sh_println!("Building batch transaction with {} call(s)...", tempo_calls.len())?;
103
104 let mut builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
106
107 if let Some(ref access_key) = tempo_access_key {
109 builder.tx.set_key_id(access_key.key_address);
110 }
111
112 builder.tx.calls = tempo_calls;
114
115 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 let tx_builder = builder.with_code_sig_and_args(None, None, vec![]).await?;
119
120 if raw_unsigned {
121 if eth.wallet.from.is_none() && !has_nonce {
122 eyre::bail!(
123 "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce"
124 );
125 }
126
127 let from = eth.wallet.from.unwrap_or(Address::ZERO);
128 let (tx, _) = tx_builder.build(from).await?;
129 let raw_tx =
130 alloy_primitives::hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing());
131 sh_println!("{raw_tx}")?;
132 return Ok(());
133 }
134
135 if ethsign {
136 let (tx, _) = tx_builder.build(config.sender).await?;
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 let from = if let Some(ref access_key) = tempo_access_key {
148 access_key.wallet_address
149 } else {
150 Signer::address(&signer)
151 };
152
153 if tempo_access_key.is_none() {
154 tx::validate_from_address(eth.wallet.from, from)?;
155 }
156
157 let (tx, _) = if tempo_access_key.is_some() {
158 tx_builder.build(from).await?
159 } else {
160 tx_builder.build(&signer).await?
161 };
162
163 let signed_tx = if let Some(ref access_key) = tempo_access_key {
164 let raw_tx = tx
165 .sign_with_access_key(
166 &provider,
167 &signer,
168 access_key.wallet_address,
169 access_key.key_address,
170 access_key.key_authorization.as_ref(),
171 )
172 .await?;
173 alloy_primitives::hex::encode(raw_tx)
174 } else {
175 let envelope = tx.build(&EthereumWallet::new(signer)).await?;
176 alloy_primitives::hex::encode(envelope.encoded_2718())
177 };
178
179 sh_println!("0x{signed_tx}")?;
180
181 Ok(())
182 }
183}