diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 78ca1d3076a63..a1803eda251fe 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -482,14 +482,17 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } CastSubcommand::Run(cmd) => cmd.run().await?, CastSubcommand::SendTx(cmd) => cmd.run().await?, - CastSubcommand::Tx { tx_hash, field, raw, rpc } => { + CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; // Can use either --raw or specify raw as a field let raw = raw || field.as_ref().is_some_and(|f| f == "raw"); - sh_println!("{}", Cast::new(&provider).transaction(tx_hash, field, raw).await?)? + sh_println!( + "{}", + Cast::new(&provider).transaction(tx_hash, from, nonce, field, raw).await? + )? } // 4Byte diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index f369c5de03f46..a83e67ef2183f 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -5,11 +5,11 @@ use alloy_consensus::TxEnvelope; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; use alloy_json_abi::Function; -use alloy_network::AnyNetwork; +use alloy_network::{AnyNetwork, AnyRpcTransaction}; use alloy_primitives::{ hex, utils::{keccak256, ParseUnits, Unit}, - Address, Keccak256, Selector, TxHash, TxKind, B256, I256, U256, + Address, Keccak256, Selector, TxHash, TxKind, B256, I256, U256, U64, }; use alloy_provider::{ network::eip2718::{Decodable2718, Encodable2718}, @@ -26,6 +26,7 @@ use foundry_block_explorers::Client; use foundry_common::{ abi::{encode_function_args, get_func}, compile::etherscan_project, + ens::NameOrAddress, fmt::*, fs, get_pretty_tx_receipt_attr, shell, TransactionReceiptWithRevertReason, }; @@ -731,26 +732,45 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let tx = cast.transaction(tx_hash.to_string(), None, false).await?; + /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?; /// println!("{}", tx); /// # Ok(()) /// # } /// ``` pub async fn transaction( &self, - tx_hash: String, + tx_hash: Option, + from: Option, + nonce: Option, field: Option, raw: bool, ) -> Result { - let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; - let tx = self - .provider - .get_transaction_by_hash(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; + let tx = if let Some(tx_hash) = tx_hash { + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + self.provider + .get_transaction_by_hash(tx_hash) + .await? + .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? + } else if let Some(from) = from { + // If nonce is not provided, uses 0. + let nonce = U64::from(nonce.unwrap_or_default()); + let from = from.resolve(self.provider.root()).await?; + + self.provider + .raw_request::<_, Option>( + "eth_getTransactionBySenderAndNonce".into(), + (from, nonce), + ) + .await? + .ok_or_else(|| { + eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) + })? + } else { + eyre::bail!("tx hash or from address is required") + }; Ok(if raw { format!("0x{}", hex::encode(tx.inner.inner.encoded_2718())) diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index 8ffb6326c025e..da789f830cda4 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -446,7 +446,15 @@ pub enum CastSubcommand { #[command(visible_alias = "t")] Tx { /// The transaction hash. - tx_hash: String, + tx_hash: Option, + + /// The sender of the transaction. + #[arg(long, value_parser = NameOrAddress::from_str)] + from: Option, + + /// Nonce of the transaction. + #[arg(long)] + nonce: Option, /// If specified, only get the given field of the transaction. If "raw", the RLP encoded /// transaction will be printed. diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 5e511256f88cd..c37ea2bbed221 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1265,6 +1265,44 @@ casttest!(tx_raw, |_prj, cmd| { "#]]); }); +casttest!(tx_using_sender_and_nonce, |_prj, cmd| { + let rpc = "https://reth-ethereum.ithaca.xyz/rpc"; + // + let args = vec![ + "tx", + "--from", + "0x4648451b5F87FF8F0F7D622bD40574bb97E25980", + "--nonce", + "113642", + "--rpc-url", + rpc, + ]; + cmd.args(args).assert_success().stdout_eq(str![[r#" + +blockHash 0x29518c1cea251b1bda5949a9b039722604ec1fb99bf9d8124cfe001c95a50bdc +blockNumber 22287055 +from 0x4648451b5F87FF8F0F7D622bD40574bb97E25980 +transactionIndex 230 +effectiveGasPrice 363392048 + +accessList [] +chainId 1 +gasLimit 350000 +hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594 +input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109 +maxFeePerGas 675979146 +maxPriorityFeePerGas 1337 +nonce 113642 +r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c +s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a +to 0xdAC17F958D2ee523a2206206994597C13D831ec7 +type 2 +value 0 +yParity 1 +... +"#]]); +}); + // ensure receipt or code is required casttest!(send_requires_to, |_prj, cmd| { cmd.args([