Skip to content

Commit 032e9a4

Browse files
feat: support for eth_sendrawtransactionSync in cast send (#12546)
* feat: support for eth_sendrawtransactionSync in cast send * conflict * fmt * alloy send_transaction_sync * doc * refactor/reuse async fn receipt and test
1 parent 504d5cb commit 032e9a4

File tree

3 files changed

+78
-19
lines changed

3 files changed

+78
-19
lines changed

crates/cast/src/cmd/send.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ pub struct SendTxArgs {
4040
#[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")]
4141
cast_async: bool,
4242

43+
/// Wait for transaction receipt synchronously instead of polling.
44+
/// Note: uses `eth_sendTransactionSync` which may not be supported by all clients.
45+
#[arg(long, conflicts_with = "async")]
46+
sync: bool,
47+
4348
/// The number of confirmations until the receipt is fetched.
4449
#[arg(long, default_value = "1")]
4550
confirmations: u64,
@@ -100,6 +105,7 @@ impl SendTxArgs {
100105
to,
101106
mut sig,
102107
cast_async,
108+
sync,
103109
mut args,
104110
tx,
105111
confirmations,
@@ -183,7 +189,7 @@ impl SendTxArgs {
183189

184190
let (tx, _) = builder.build(config.sender).await?;
185191

186-
cast_send(provider, tx, cast_async, confirmations, timeout).await
192+
cast_send(provider, tx, cast_async, sync, confirmations, timeout).await
187193
// Case 2:
188194
// An option to use a local signer was provided.
189195
// If we cannot successfully instantiate a local signer, then we will assume we don't have
@@ -221,7 +227,7 @@ impl SendTxArgs {
221227
.wallet(wallet)
222228
.connect_provider(&provider);
223229

224-
cast_send(provider, tx_request, cast_async, confirmations, timeout).await
230+
cast_send(provider, tx_request, cast_async, sync, confirmations, timeout).await
225231
}
226232
}
227233
}
@@ -230,19 +236,27 @@ async fn cast_send<P: Provider<AnyNetwork>>(
230236
provider: P,
231237
tx: WithOtherFields<TransactionRequest>,
232238
cast_async: bool,
239+
sync: bool,
233240
confs: u64,
234241
timeout: u64,
235242
) -> Result<()> {
236-
let cast = Cast::new(provider);
237-
let pending_tx = cast.send(tx).await?;
238-
let tx_hash = pending_tx.inner().tx_hash();
243+
let cast = Cast::new(&provider);
239244

240-
if cast_async {
241-
sh_println!("{tx_hash:#x}")?;
242-
} else {
243-
let receipt =
244-
cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
245+
if sync {
246+
// Send transaction and wait for receipt synchronously
247+
let receipt = cast.send_sync(tx).await?;
245248
sh_println!("{receipt}")?;
249+
} else {
250+
let pending_tx = cast.send(tx).await?;
251+
let tx_hash = pending_tx.inner().tx_hash();
252+
253+
if cast_async {
254+
sh_println!("{tx_hash:#x}")?;
255+
} else {
256+
let receipt =
257+
cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
258+
sh_println!("{receipt}")?;
259+
}
246260
}
247261

248262
Ok(())

crates/cast/src/lib.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,34 @@ impl<P: Provider<AnyNetwork>> Cast<P> {
330330
Ok(res)
331331
}
332332

333+
/// Sends a transaction and waits for receipt synchronously
334+
pub async fn send_sync(&self, tx: WithOtherFields<TransactionRequest>) -> Result<String> {
335+
let mut receipt: TransactionReceiptWithRevertReason =
336+
self.provider.send_transaction_sync(tx).await?.into();
337+
338+
// Allow to fail silently
339+
let _ = receipt.update_revert_reason(&self.provider).await;
340+
341+
self.format_receipt(receipt, None)
342+
}
343+
344+
/// Helper method to format transaction receipts consistently
345+
fn format_receipt(
346+
&self,
347+
receipt: TransactionReceiptWithRevertReason,
348+
field: Option<String>,
349+
) -> Result<String> {
350+
Ok(if let Some(ref field) = field {
351+
get_pretty_tx_receipt_attr(&receipt, field)
352+
.ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))?
353+
} else if shell::is_json() {
354+
// to_value first to sort json object keys
355+
serde_json::to_value(&receipt)?.to_string()
356+
} else {
357+
receipt.pretty()
358+
})
359+
}
360+
333361
/// # Example
334362
///
335363
/// ```
@@ -863,15 +891,7 @@ impl<P: Provider<AnyNetwork>> Cast<P> {
863891
// Allow to fail silently
864892
let _ = receipt.update_revert_reason(&self.provider).await;
865893

866-
Ok(if let Some(ref field) = field {
867-
get_pretty_tx_receipt_attr(&receipt, field)
868-
.ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))?
869-
} else if shell::is_json() {
870-
// to_value first to sort json object keys
871-
serde_json::to_value(&receipt)?.to_string()
872-
} else {
873-
receipt.pretty()
874-
})
894+
self.format_receipt(receipt, field)
875895
}
876896

877897
/// Perform a raw JSON-RPC request

crates/cast/tests/cli/main.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,6 +2497,31 @@ casttest!(send_eip7702, async |_prj, cmd| {
24972497
"#]]);
24982498
});
24992499

2500+
casttest!(send_sync, async |_prj, cmd| {
2501+
let (_api, handle) = anvil::spawn(NodeConfig::test()).await;
2502+
let endpoint = handle.http_endpoint();
2503+
2504+
let output = cmd
2505+
.args([
2506+
"send",
2507+
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
2508+
"--value",
2509+
"1",
2510+
"--private-key",
2511+
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
2512+
"--rpc-url",
2513+
&endpoint,
2514+
"--sync",
2515+
])
2516+
.assert_success()
2517+
.get_output()
2518+
.stdout_lossy();
2519+
2520+
assert!(output.contains("transactionHash"));
2521+
assert!(output.contains("blockNumber"));
2522+
assert!(output.contains("gasUsed"));
2523+
});
2524+
25002525
casttest!(hash_message, |_prj, cmd| {
25012526
cmd.args(["hash-message", "hello"]).assert_success().stdout_eq(str![[r#"
25022527
0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750

0 commit comments

Comments
 (0)