Skip to main content

cast/cmd/
batch_mktx.rs

1//! `cast batch-mktx` command implementation.
2//!
3//! Creates a signed or unsigned batch transaction using Tempo's native call batching.
4//! Outputs the RLP-encoded transaction hex.
5
6use crate::{
7    cmd::{
8        auth::{confirm_and_build, confirm_and_build_with_tempo_wallet},
9        batch_send::with_batch_calls,
10    },
11    tempo,
12    tx::{self, CastTxBuilder},
13};
14use alloy_consensus::SignableTransaction;
15use alloy_eips::eip2718::Encodable2718;
16use alloy_network::{EthereumWallet, NetworkTransactionBuilder};
17use alloy_primitives::{Address, hex};
18use alloy_provider::Provider;
19use clap::Parser;
20use eyre::Result;
21use foundry_cli::{
22    opts::{EthereumOpts, TransactionOpts},
23    utils::{self, resolve_lane},
24};
25use foundry_common::FoundryTransactionBuilder;
26use tempo_alloy::TempoNetwork;
27
28/// CLI arguments for `cast batch-mktx`.
29///
30/// Creates a signed (or unsigned) batch transaction.
31#[derive(Debug, Parser)]
32pub struct BatchMakeTxArgs {
33    /// Call specifications in format: `to[:<value>][:<sig>[:<args>]]` or `to[:<value>][:<0xdata>]`
34    ///
35    /// Examples:
36    ///   --call "0x123:0.1ether" (ETH transfer)
37    ///   --call "0x456::transfer(address,uint256):0x789,1000" (ERC20 transfer)
38    ///   --call "0xabc::0x123def" (raw calldata)
39    #[arg(long = "call", value_name = "SPEC", required = true)]
40    pub calls: Vec<String>,
41
42    #[command(flatten)]
43    pub tx: TransactionOpts,
44
45    /// Skip the EIP-7702 authorization disclosure confirmation.
46    #[arg(long)]
47    pub force: bool,
48
49    #[command(flatten)]
50    pub eth: EthereumOpts,
51
52    /// Generate a raw RLP-encoded unsigned transaction.
53    #[arg(long)]
54    pub raw_unsigned: bool,
55
56    /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender
57    #[arg(long, requires = "from", conflicts_with = "raw_unsigned")]
58    pub ethsign: bool,
59}
60
61impl BatchMakeTxArgs {
62    pub async fn run(self) -> Result<()> {
63        let Self { calls, mut tx, force, eth, raw_unsigned, ethsign } = self;
64        let has_nonce = tx.nonce.is_some();
65        let has_session = tx.tempo.session_id()?.is_some();
66        let expires_at = tx.tempo.resolve_expires();
67
68        if has_session && raw_unsigned {
69            eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --raw-unsigned");
70        }
71        if has_session && ethsign {
72            eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --ethsign");
73        }
74
75        let (config, provider) = tempo::tempo_provider(&eth)?;
76        // The provider is not consulted for fee tokens in `--curl` mode.
77        let fee_provider = (!config.eth_rpc_curl).then_some(&provider);
78        let resolved_lane = resolve_lane(&mut tx.tempo, &config.root)?;
79        let lane = resolved_lane.as_ref();
80
81        let chain = utils::get_chain(config.chain, &provider).await?;
82        // A raw unsigned transaction needs no signer, but the access-key metadata still shapes
83        // the request.
84        let (signer, tempo_access_key) = if raw_unsigned {
85            (None, eth.wallet.maybe_signer_for_chain(chain.id()).await?.1)
86        } else {
87            tempo::resolve_session_or_wallet_signer(&tx.tempo, &eth.wallet, chain.id()).await?
88        };
89
90        // Preserve key_id for modes that do not call build_with_tempo_wallet, such as raw unsigned.
91        if let Some(access_key) = &tempo_access_key {
92            tx.tempo.key_id = Some(access_key.key_id()?);
93        }
94
95        let builder = CastTxBuilder::<TempoNetwork, _, _>::new(&provider, tx, &config).await?;
96        let tx_builder = with_batch_calls(&calls, builder, &provider).await?;
97        tempo::print_expires(expires_at)?;
98
99        if raw_unsigned {
100            if eth.wallet.from.is_none() && !has_nonce {
101                eyre::bail!(
102                    "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce"
103                );
104            }
105
106            let from = eth.wallet.from.unwrap_or(Address::ZERO);
107            let Some(mut tx) = confirm_and_build(tx_builder, from, force, lane, false).await?
108            else {
109                return Ok(());
110            };
111            tempo::resolve_and_print_fee_token(fee_provider, Some(chain), &mut tx, Some(from))
112                .await?;
113            let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing());
114            sh_println!("{raw_tx}")?;
115            return Ok(());
116        }
117
118        if ethsign {
119            let Some(mut tx) =
120                confirm_and_build(tx_builder, config.sender, force, lane, true).await?
121            else {
122                return Ok(());
123            };
124            tempo::resolve_and_print_fee_token(
125                fee_provider,
126                Some(chain),
127                &mut tx,
128                Some(config.sender),
129            )
130            .await?;
131            let signed_tx = provider.sign_transaction(tx).await?;
132            sh_println!("{signed_tx}")?;
133            return Ok(());
134        }
135
136        let signed_tx = if let Some(access_key) = &tempo_access_key {
137            let Some((mut tx, prepared)) =
138                confirm_and_build_with_tempo_wallet(tx_builder, access_key, force, lane).await?
139            else {
140                return Ok(());
141            };
142            tempo::resolve_and_print_fee_token(
143                fee_provider,
144                Some(chain),
145                &mut tx,
146                Some(prepared.account()),
147            )
148            .await?;
149            tx.sign_with_tempo_wallet(&prepared).await?
150        } else {
151            let (signer, from) = tx::resolve_send_signer(signer, &eth).await?;
152            let Some(mut tx) = confirm_and_build(tx_builder, &signer, force, lane, false).await?
153            else {
154                return Ok(());
155            };
156            tempo::resolve_and_print_fee_token(fee_provider, Some(chain), &mut tx, Some(from))
157                .await?;
158            tx.build(&EthereumWallet::new(signer)).await?.encoded_2718()
159        };
160
161        sh_println!("{}", hex::encode_prefixed(signed_tx))?;
162
163        Ok(())
164    }
165}