Skip to content

Commit 23b1cbd

Browse files
authored
feat(cast): getTransactionBySenderAndNonce (#10323)
* feat(`cast`): getTransactionBySenderAndNonce * fix doc-test
1 parent 1da4d32 commit 23b1cbd

File tree

4 files changed

+83
-14
lines changed

4 files changed

+83
-14
lines changed

crates/cast/src/args.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,14 +482,17 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
482482
}
483483
CastSubcommand::Run(cmd) => cmd.run().await?,
484484
CastSubcommand::SendTx(cmd) => cmd.run().await?,
485-
CastSubcommand::Tx { tx_hash, field, raw, rpc } => {
485+
CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc } => {
486486
let config = rpc.load_config()?;
487487
let provider = utils::get_provider(&config)?;
488488

489489
// Can use either --raw or specify raw as a field
490490
let raw = raw || field.as_ref().is_some_and(|f| f == "raw");
491491

492-
sh_println!("{}", Cast::new(&provider).transaction(tx_hash, field, raw).await?)?
492+
sh_println!(
493+
"{}",
494+
Cast::new(&provider).transaction(tx_hash, from, nonce, field, raw).await?
495+
)?
493496
}
494497

495498
// 4Byte

crates/cast/src/lib.rs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
use alloy_consensus::TxEnvelope;
66
use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt};
77
use alloy_json_abi::Function;
8-
use alloy_network::AnyNetwork;
8+
use alloy_network::{AnyNetwork, AnyRpcTransaction};
99
use alloy_primitives::{
1010
hex,
1111
utils::{keccak256, ParseUnits, Unit},
12-
Address, Keccak256, Selector, TxHash, TxKind, B256, I256, U256,
12+
Address, Keccak256, Selector, TxHash, TxKind, B256, I256, U256, U64,
1313
};
1414
use alloy_provider::{
1515
network::eip2718::{Decodable2718, Encodable2718},
@@ -26,6 +26,7 @@ use foundry_block_explorers::Client;
2626
use foundry_common::{
2727
abi::{encode_function_args, get_func},
2828
compile::etherscan_project,
29+
ens::NameOrAddress,
2930
fmt::*,
3031
fs, get_pretty_tx_receipt_attr, shell, TransactionReceiptWithRevertReason,
3132
};
@@ -731,26 +732,45 @@ impl<P: Provider<AnyNetwork>> Cast<P> {
731732
///
732733
/// # async fn foo() -> eyre::Result<()> {
733734
/// let provider =
734-
/// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;
735+
/// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?;
735736
/// let cast = Cast::new(provider);
736737
/// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc";
737-
/// let tx = cast.transaction(tx_hash.to_string(), None, false).await?;
738+
/// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?;
738739
/// println!("{}", tx);
739740
/// # Ok(())
740741
/// # }
741742
/// ```
742743
pub async fn transaction(
743744
&self,
744-
tx_hash: String,
745+
tx_hash: Option<String>,
746+
from: Option<NameOrAddress>,
747+
nonce: Option<u64>,
745748
field: Option<String>,
746749
raw: bool,
747750
) -> Result<String> {
748-
let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?;
749-
let tx = self
750-
.provider
751-
.get_transaction_by_hash(tx_hash)
752-
.await?
753-
.ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?;
751+
let tx = if let Some(tx_hash) = tx_hash {
752+
let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?;
753+
self.provider
754+
.get_transaction_by_hash(tx_hash)
755+
.await?
756+
.ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?
757+
} else if let Some(from) = from {
758+
// If nonce is not provided, uses 0.
759+
let nonce = U64::from(nonce.unwrap_or_default());
760+
let from = from.resolve(self.provider.root()).await?;
761+
762+
self.provider
763+
.raw_request::<_, Option<AnyRpcTransaction>>(
764+
"eth_getTransactionBySenderAndNonce".into(),
765+
(from, nonce),
766+
)
767+
.await?
768+
.ok_or_else(|| {
769+
eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::<u64>())
770+
})?
771+
} else {
772+
eyre::bail!("tx hash or from address is required")
773+
};
754774

755775
Ok(if raw {
756776
format!("0x{}", hex::encode(tx.inner.inner.encoded_2718()))

crates/cast/src/opts.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,15 @@ pub enum CastSubcommand {
446446
#[command(visible_alias = "t")]
447447
Tx {
448448
/// The transaction hash.
449-
tx_hash: String,
449+
tx_hash: Option<String>,
450+
451+
/// The sender of the transaction.
452+
#[arg(long, value_parser = NameOrAddress::from_str)]
453+
from: Option<NameOrAddress>,
454+
455+
/// Nonce of the transaction.
456+
#[arg(long)]
457+
nonce: Option<u64>,
450458

451459
/// If specified, only get the given field of the transaction. If "raw", the RLP encoded
452460
/// transaction will be printed.

crates/cast/tests/cli/main.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,44 @@ casttest!(tx_raw, |_prj, cmd| {
12651265
"#]]);
12661266
});
12671267

1268+
casttest!(tx_using_sender_and_nonce, |_prj, cmd| {
1269+
let rpc = "https://reth-ethereum.ithaca.xyz/rpc";
1270+
// <https://etherscan.io/tx/0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594>
1271+
let args = vec![
1272+
"tx",
1273+
"--from",
1274+
"0x4648451b5F87FF8F0F7D622bD40574bb97E25980",
1275+
"--nonce",
1276+
"113642",
1277+
"--rpc-url",
1278+
rpc,
1279+
];
1280+
cmd.args(args).assert_success().stdout_eq(str![[r#"
1281+
1282+
blockHash 0x29518c1cea251b1bda5949a9b039722604ec1fb99bf9d8124cfe001c95a50bdc
1283+
blockNumber 22287055
1284+
from 0x4648451b5F87FF8F0F7D622bD40574bb97E25980
1285+
transactionIndex 230
1286+
effectiveGasPrice 363392048
1287+
1288+
accessList []
1289+
chainId 1
1290+
gasLimit 350000
1291+
hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594
1292+
input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109
1293+
maxFeePerGas 675979146
1294+
maxPriorityFeePerGas 1337
1295+
nonce 113642
1296+
r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c
1297+
s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a
1298+
to 0xdAC17F958D2ee523a2206206994597C13D831ec7
1299+
type 2
1300+
value 0
1301+
yParity 1
1302+
...
1303+
"#]]);
1304+
});
1305+
12681306
// ensure receipt or code is required
12691307
casttest!(send_requires_to, |_prj, cmd| {
12701308
cmd.args([

0 commit comments

Comments
 (0)