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