cast/cmd/
mktx.rs

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