Skip to content

Commit ca8e4ab

Browse files
committed
feat(cast): make unsigned raw txs
1 parent 1f22112 commit ca8e4ab

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

crates/cast/bin/cmd/mktx.rs

+27-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder};
33
use alloy_primitives::hex;
44
use alloy_signer::Signer;
55
use clap::Parser;
6-
use eyre::Result;
6+
use eyre::{OptionExt, Result};
77
use foundry_cli::{
88
opts::{EthereumOpts, TransactionOpts},
99
utils::{get_provider, LoadConfig},
@@ -44,6 +44,12 @@ pub struct MakeTxArgs {
4444

4545
#[command(flatten)]
4646
eth: EthereumOpts,
47+
48+
/// Generate a raw RLP-encoded unsigned transaction.
49+
///
50+
/// Relaxes the wallet requirement.
51+
#[arg(long, requires = "from")]
52+
raw_unsigned: bool,
4753
}
4854

4955
#[derive(Debug, Parser)]
@@ -64,7 +70,7 @@ pub enum MakeTxSubcommands {
6470

6571
impl MakeTxArgs {
6672
pub async fn run(self) -> Result<()> {
67-
let Self { to, mut sig, mut args, command, tx, path, eth } = self;
73+
let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned } = self;
6874

6975
let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
7076

@@ -83,23 +89,32 @@ impl MakeTxArgs {
8389

8490
let config = eth.load_config()?;
8591

86-
// Retrieve the signer, and bail if it can't be constructed.
87-
let signer = eth.wallet.signer().await?;
88-
let from = signer.address();
89-
90-
tx::validate_from_address(eth.wallet.from, from)?;
91-
9292
let provider = get_provider(&config)?;
9393

94-
let (tx, _) = CastTxBuilder::new(provider, tx, &config)
94+
let tx_builder = CastTxBuilder::new(provider, tx, &config)
9595
.await?
9696
.with_to(to)
9797
.await?
9898
.with_code_sig_and_args(code, sig, args)
9999
.await?
100-
.with_blob_data(blob_data)?
101-
.build(&signer)
102-
.await?;
100+
.with_blob_data(blob_data)?;
101+
102+
if raw_unsigned {
103+
// Build unsigned raw tx
104+
let from = eth.wallet.from.ok_or_eyre("missing `--from` address")?;
105+
let raw_tx = tx_builder.build_unsigned_raw(from).await?;
106+
107+
sh_println!("{raw_tx}")?;
108+
return Ok(());
109+
}
110+
111+
// Retrieve the signer, and bail if it can't be constructed.
112+
let signer = eth.wallet.signer().await?;
113+
let from = signer.address();
114+
115+
tx::validate_from_address(eth.wallet.from, from)?;
116+
117+
let (tx, _) = tx_builder.build(&signer).await?;
103118

104119
let tx = tx.build(&EthereumWallet::new(signer)).await?;
105120

crates/cast/bin/tx.rs

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use alloy_consensus::{SidecarBuilder, SimpleCoder};
1+
use alloy_consensus::{SidecarBuilder, SignableTransaction, SimpleCoder};
22
use alloy_dyn_abi::ErrorExt;
33
use alloy_json_abi::Function;
44
use alloy_network::{
5-
AnyNetwork, TransactionBuilder, TransactionBuilder4844, TransactionBuilder7702,
5+
AnyNetwork, AnyTypedTransaction, TransactionBuilder, TransactionBuilder4844,
6+
TransactionBuilder7702,
67
};
78
use alloy_primitives::{hex, Address, Bytes, TxKind, U256};
89
use alloy_provider::Provider;
@@ -277,7 +278,7 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InputState> {
277278
self,
278279
sender: impl Into<SenderKind<'_>>,
279280
) -> Result<(WithOtherFields<TransactionRequest>, Option<Function>)> {
280-
self._build(sender, true).await
281+
self._build(sender, true, false).await
281282
}
282283

283284
/// Builds [TransactionRequest] without filling missing fields. Used for read-only calls such as
@@ -286,13 +287,26 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InputState> {
286287
self,
287288
sender: impl Into<SenderKind<'_>>,
288289
) -> Result<(WithOtherFields<TransactionRequest>, Option<Function>)> {
289-
self._build(sender, false).await
290+
self._build(sender, false, false).await
291+
}
292+
293+
/// Builds an unsigned RLP-encoded raw transaction.
294+
///
295+
/// Returns the hex encoded string representation of the transaction.
296+
pub async fn build_unsigned_raw(self, from: Address) -> Result<String> {
297+
let (tx, _) = self._build(SenderKind::Address(from), true, true).await?;
298+
let tx = tx.build_unsigned()?;
299+
match tx {
300+
AnyTypedTransaction::Ethereum(t) => Ok(hex::encode_prefixed(t.encoded_for_signing())),
301+
_ => eyre::bail!("Cannot generate unsigned transaction for non-Ethereum transactions"),
302+
}
290303
}
291304

292305
async fn _build(
293306
mut self,
294307
sender: impl Into<SenderKind<'_>>,
295308
fill: bool,
309+
unsigned: bool,
296310
) -> Result<(WithOtherFields<TransactionRequest>, Option<Function>)> {
297311
let sender = sender.into();
298312
let from = sender.address();
@@ -316,7 +330,17 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InputState> {
316330
nonce
317331
};
318332

319-
self.resolve_auth(sender, tx_nonce).await?;
333+
if !unsigned {
334+
self.resolve_auth(sender, tx_nonce).await?;
335+
} else if self.auth.is_some() {
336+
let Some(CliAuthorizationList::Signed(signed_auth)) = self.auth.take() else {
337+
eyre::bail!(
338+
"SignedAuthorization needs to be provided for generating unsigned 7702 txs"
339+
)
340+
};
341+
342+
self.tx.set_authorization_list(vec![signed_auth]);
343+
}
320344

321345
if let Some(access_list) = match self.access_list.take() {
322346
None => None,

crates/cast/tests/cli/main.rs

+26
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,32 @@ casttest!(mktx_signer_from_match, |_prj, cmd| {
11101110
"#]]);
11111111
});
11121112

1113+
casttest!(mktx_raw_unsigned, |_prj, cmd| {
1114+
cmd.args([
1115+
"mktx",
1116+
"--from",
1117+
"0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf",
1118+
"--chain",
1119+
"1",
1120+
"--nonce",
1121+
"0",
1122+
"--gas-limit",
1123+
"21000",
1124+
"--gas-price",
1125+
"10000000000",
1126+
"--priority-gas-price",
1127+
"1000000000",
1128+
"0x0000000000000000000000000000000000000001",
1129+
"--raw-unsigned",
1130+
])
1131+
.assert_success()
1132+
.stdout_eq(str![[
1133+
r#"0x02e80180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c0
1134+
1135+
"#
1136+
]]);
1137+
});
1138+
11131139
// tests that the raw encoded transaction is returned
11141140
casttest!(tx_raw, |_prj, cmd| {
11151141
let rpc = next_http_rpc_endpoint();

0 commit comments

Comments
 (0)