Skip to main content

cast/cmd/
mktx.rs

1use crate::tx::{self, CastTxBuilder};
2use alloy_consensus::{SignableTransaction, Signed};
3use alloy_eips::Encodable2718;
4use alloy_ens::NameOrAddress;
5use alloy_network::{AnyNetwork, EthereumWallet, Network, TransactionBuilder};
6use alloy_primitives::{Address, hex};
7use alloy_provider::Provider;
8use alloy_signer::{Signature, Signer};
9use clap::Parser;
10use eyre::Result;
11use foundry_cli::{
12    opts::{EthereumOpts, TransactionOpts},
13    utils::LoadConfig,
14};
15use foundry_common::provider::ProviderBuilder;
16use foundry_primitives::FoundryTransactionBuilder;
17use std::{path::PathBuf, str::FromStr};
18use tempo_alloy::TempoNetwork;
19
20/// CLI arguments for `cast mktx`.
21#[derive(Debug, Parser)]
22pub struct MakeTxArgs {
23    /// The destination of the transaction.
24    ///
25    /// If not provided, you must use `cast mktx --create`.
26    #[arg(value_parser = NameOrAddress::from_str)]
27    to: Option<NameOrAddress>,
28
29    /// The signature of the function to call.
30    sig: Option<String>,
31
32    /// The arguments of the function to call.
33    #[arg(allow_negative_numbers = true)]
34    args: Vec<String>,
35
36    #[command(subcommand)]
37    command: Option<MakeTxSubcommands>,
38
39    #[command(flatten)]
40    tx: TransactionOpts,
41
42    /// The path of blob data to be sent.
43    #[arg(
44        long,
45        value_name = "BLOB_DATA_PATH",
46        conflicts_with = "legacy",
47        requires = "blob",
48        help_heading = "Transaction options"
49    )]
50    path: Option<PathBuf>,
51
52    #[command(flatten)]
53    eth: EthereumOpts,
54
55    /// Generate a raw RLP-encoded unsigned transaction.
56    ///
57    /// Relaxes the wallet requirement.
58    #[arg(long)]
59    raw_unsigned: bool,
60
61    /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender
62    #[arg(long, requires = "from", conflicts_with = "raw_unsigned")]
63    ethsign: bool,
64}
65
66#[derive(Debug, Parser)]
67pub enum MakeTxSubcommands {
68    /// Use to deploy raw contract bytecode.
69    #[command(name = "--create")]
70    Create {
71        /// The initialization bytecode of the contract to deploy.
72        code: String,
73
74        /// The signature of the constructor.
75        sig: Option<String>,
76
77        /// The constructor arguments.
78        #[arg(allow_negative_numbers = true)]
79        args: Vec<String>,
80    },
81}
82
83impl MakeTxArgs {
84    pub async fn run(self) -> Result<()> {
85        if self.tx.tempo.is_tempo() {
86            self.run_generic::<TempoNetwork>().await
87        } else {
88            self.run_generic::<AnyNetwork>().await
89        }
90    }
91
92    pub async fn run_generic<N: Network>(self) -> Result<()>
93    where
94        N::TxEnvelope: From<Signed<N::UnsignedTx>>,
95        N::UnsignedTx: SignableTransaction<Signature>,
96        N::TransactionRequest: FoundryTransactionBuilder<N>,
97    {
98        let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self;
99
100        let print_sponsor_hash = tx.tempo.print_sponsor_hash;
101
102        let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
103
104        let code = if let Some(MakeTxSubcommands::Create {
105            code,
106            sig: constructor_sig,
107            args: constructor_args,
108        }) = command
109        {
110            sig = constructor_sig;
111            args = constructor_args;
112            Some(code)
113        } else {
114            None
115        };
116
117        let config = eth.load_config()?;
118
119        let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
120
121        let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config)
122            .await?
123            .with_to(to)
124            .await?
125            .with_code_sig_and_args(code, sig, args)
126            .await?
127            .with_blob_data(blob_data)?;
128
129        // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit.
130        if print_sponsor_hash {
131            let from = eth.wallet.from.unwrap_or(Address::ZERO);
132            let (tx, _) = tx_builder.build(from).await?;
133            let hash = tx.compute_sponsor_hash(from).ok_or_else(|| {
134                eyre::eyre!("This network does not support sponsored transactions")
135            })?;
136            sh_println!("{hash:?}")?;
137            return Ok(());
138        }
139
140        if raw_unsigned {
141            // Build unsigned raw tx
142            // Check if nonce is provided when --from is not specified
143            // See: <https://github.com/foundry-rs/foundry/issues/11110>
144            if eth.wallet.from.is_none() && tx.nonce.is_none() {
145                eyre::bail!(
146                    "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce"
147                );
148            }
149
150            // Use zero address as placeholder for unsigned transactions
151            let from = eth.wallet.from.unwrap_or(Address::ZERO);
152
153            let (tx, _) = tx_builder.build(from).await?;
154            let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing());
155
156            sh_println!("{raw_tx}")?;
157            return Ok(());
158        }
159
160        if ethsign {
161            // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has
162            // unlocked accounts.
163            let (tx, _) = tx_builder.build(config.sender).await?;
164            let signed_tx = provider.sign_transaction(tx).await?;
165
166            sh_println!("{signed_tx}")?;
167            return Ok(());
168        }
169
170        // Default to using the local signer.
171        // Get the signer from the wallet, and fail if it can't be constructed.
172        let signer = eth.wallet.signer().await?;
173        let from = signer.address();
174
175        tx::validate_from_address(eth.wallet.from, from)?;
176
177        let (tx, _) = tx_builder.build(&signer).await?;
178
179        let tx = tx.build(&EthereumWallet::new(signer)).await?;
180
181        let signed_tx = hex::encode(tx.encoded_2718());
182        sh_println!("0x{signed_tx}")?;
183
184        Ok(())
185    }
186}