cast/cmd/
mktx.rs

1use crate::tx::{self, CastTxBuilder};
2use alloy_ens::NameOrAddress;
3use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder};
4use alloy_primitives::hex;
5use alloy_provider::Provider;
6use alloy_signer::Signer;
7use clap::Parser;
8use eyre::{OptionExt, Result};
9use foundry_cli::{
10    opts::{EthereumOpts, TransactionOpts},
11    utils::{get_provider, LoadConfig},
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    args: Vec<String>,
29
30    #[command(subcommand)]
31    command: Option<MakeTxSubcommands>,
32
33    #[command(flatten)]
34    tx: TransactionOpts,
35
36    /// The path of blob data to be sent.
37    #[arg(
38        long,
39        value_name = "BLOB_DATA_PATH",
40        conflicts_with = "legacy",
41        requires = "blob",
42        help_heading = "Transaction options"
43    )]
44    path: Option<PathBuf>,
45
46    #[command(flatten)]
47    eth: EthereumOpts,
48
49    /// Generate a raw RLP-encoded unsigned transaction.
50    ///
51    /// Relaxes the wallet requirement.
52    #[arg(long, requires = "from")]
53    raw_unsigned: bool,
54
55    /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender
56    #[arg(long, requires = "from", conflicts_with = "raw_unsigned")]
57    ethsign: bool,
58}
59
60#[derive(Debug, Parser)]
61pub enum MakeTxSubcommands {
62    /// Use to deploy raw contract bytecode.
63    #[command(name = "--create")]
64    Create {
65        /// The initialization bytecode of the contract to deploy.
66        code: String,
67
68        /// The signature of the constructor.
69        sig: Option<String>,
70
71        /// The constructor arguments.
72        args: Vec<String>,
73    },
74}
75
76impl MakeTxArgs {
77    pub async fn run(self) -> Result<()> {
78        let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self;
79
80        let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
81
82        let code = if let Some(MakeTxSubcommands::Create {
83            code,
84            sig: constructor_sig,
85            args: constructor_args,
86        }) = command
87        {
88            sig = constructor_sig;
89            args = constructor_args;
90            Some(code)
91        } else {
92            None
93        };
94
95        let config = eth.load_config()?;
96
97        let provider = get_provider(&config)?;
98
99        let tx_builder = CastTxBuilder::new(&provider, tx, &config)
100            .await?
101            .with_to(to)
102            .await?
103            .with_code_sig_and_args(code, sig, args)
104            .await?
105            .with_blob_data(blob_data)?;
106
107        if raw_unsigned {
108            // Build unsigned raw tx
109            let from = eth.wallet.from.ok_or_eyre("missing `--from` address")?;
110            let raw_tx = tx_builder.build_unsigned_raw(from).await?;
111
112            sh_println!("{raw_tx}")?;
113            return Ok(());
114        }
115
116        if ethsign {
117            // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has
118            // unlocked accounts.
119            let (tx, _) = tx_builder.build(config.sender).await?;
120            let signed_tx = provider.sign_transaction(tx).await?;
121
122            sh_println!("{signed_tx}")?;
123            return Ok(());
124        }
125
126        // Default to using the local signer.
127        // Get the signer from the wallet, and fail if it can't be constructed.
128        let signer = eth.wallet.signer().await?;
129        let from = signer.address();
130
131        tx::validate_from_address(eth.wallet.from, from)?;
132
133        let (tx, _) = tx_builder.build(&signer).await?;
134
135        let tx = tx.build(&EthereumWallet::new(signer)).await?;
136
137        let signed_tx = hex::encode(tx.encoded_2718());
138        sh_println!("0x{signed_tx}")?;
139
140        Ok(())
141    }
142}