From 0586997ccec37839973467ef7c4d3a033283ff52 Mon Sep 17 00:00:00 2001
From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com>
Date: Thu, 17 Apr 2025 12:38:54 +0530
Subject: [PATCH 1/2] feat(`cast`): getTransactionBySenderAndNonce

---
 crates/cast/src/args.rs       |  7 ++++--
 crates/cast/src/lib.rs        | 40 ++++++++++++++++++++++++++---------
 crates/cast/src/opts.rs       | 10 ++++++++-
 crates/cast/tests/cli/main.rs | 38 +++++++++++++++++++++++++++++++++
 4 files changed, 82 insertions(+), 13 deletions(-)

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..04e0f1739023b 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,
 };
@@ -734,23 +735,42 @@ impl<P: Provider<AnyNetwork>> Cast<P> {
     ///     ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("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, false).await?;
     /// println!("{}", tx);
     /// # Ok(())
     /// # }
     /// ```
     pub async fn transaction(
         &self,
-        tx_hash: String,
+        tx_hash: Option<String>,
+        from: Option<NameOrAddress>,
+        nonce: Option<u64>,
         field: Option<String>,
         raw: bool,
     ) -> Result<String> {
-        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<AnyRpcTransaction>>(
+                    "eth_getTransactionBySenderAndNonce".into(),
+                    (from, nonce),
+                )
+                .await?
+                .ok_or_else(|| {
+                    eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::<u64>())
+                })?
+        } 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<String>,
+
+        /// The sender of the transaction.
+        #[arg(long, value_parser = NameOrAddress::from_str)]
+        from: Option<NameOrAddress>,
+
+        /// Nonce of the transaction.
+        #[arg(long)]
+        nonce: Option<u64>,
 
         /// 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";
+    // <https://etherscan.io/tx/0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594>
+    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([

From 629ed6fae558f2b023d8518afe3f09ced8358f53 Mon Sep 17 00:00:00 2001
From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com>
Date: Thu, 17 Apr 2025 12:48:21 +0530
Subject: [PATCH 2/2] fix doc-test

---
 crates/cast/src/lib.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs
index 04e0f1739023b..a83e67ef2183f 100644
--- a/crates/cast/src/lib.rs
+++ b/crates/cast/src/lib.rs
@@ -732,10 +732,10 @@ impl<P: Provider<AnyNetwork>> Cast<P> {
     ///
     /// # 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(Some(tx_hash.to_string()), None, false).await?;
+    /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?;
     /// println!("{}", tx);
     /// # Ok(())
     /// # }