Skip to content

feat(cast): getTransactionBySenderAndNonce #10323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions crates/cast/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 31 additions & 11 deletions crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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,
};
Expand Down Expand Up @@ -731,26 +732,45 @@ 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(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<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()))
Expand Down
10 changes: 9 additions & 1 deletion crates/cast/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
38 changes: 38 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down