Skip to main content

foundry_cli/opts/
transaction.rs

1use std::str::FromStr;
2
3use super::TempoOpts;
4use crate::utils::{parse_ether_value, parse_json};
5use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
6use alloy_network::{Network, TransactionBuilder};
7use alloy_primitives::{Address, U64, U256, hex};
8use alloy_rlp::Decodable;
9use clap::Parser;
10use foundry_common::FoundryTransactionBuilder;
11
12/// CLI helper to parse a EIP-7702 authorization list.
13/// Can be either a hex-encoded signed authorization or an address.
14#[derive(Clone, Debug)]
15pub enum CliAuthorizationList {
16    /// If an address is provided, we sign the authorization delegating to provided address.
17    Address(Address),
18    /// If RLP-encoded authorization is provided, we decode it and attach to transaction.
19    Signed(SignedAuthorization),
20}
21
22impl FromStr for CliAuthorizationList {
23    type Err = eyre::Error;
24
25    fn from_str(s: &str) -> Result<Self, Self::Err> {
26        if let Ok(addr) = Address::from_str(s) {
27            Ok(Self::Address(addr))
28        } else if let Ok(auth) = SignedAuthorization::decode(&mut hex::decode(s)?.as_ref()) {
29            Ok(Self::Signed(auth))
30        } else {
31            eyre::bail!("Failed to decode authorization")
32        }
33    }
34}
35
36#[derive(Clone, Debug, Parser)]
37#[command(next_help_heading = "Transaction options")]
38pub struct TransactionOpts {
39    /// Gas limit for the transaction.
40    #[arg(long, env = "ETH_GAS_LIMIT")]
41    pub gas_limit: Option<U256>,
42
43    /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions, either
44    /// specified in wei, or as a string with a unit type.
45    ///
46    /// Examples: 1ether, 10gwei, 0.01ether
47    #[arg(
48        long,
49        env = "ETH_GAS_PRICE",
50        value_parser = parse_ether_value,
51        value_name = "PRICE"
52    )]
53    pub gas_price: Option<U256>,
54
55    /// Max priority fee per gas for EIP1559 transactions.
56    #[arg(
57        long,
58        env = "ETH_PRIORITY_GAS_PRICE",
59        value_parser = parse_ether_value,
60        value_name = "PRICE"
61    )]
62    pub priority_gas_price: Option<U256>,
63
64    /// Ether to send in the transaction, either specified in wei, or as a string with a unit type.
65    ///
66    ///
67    ///
68    /// Examples: 1ether, 10gwei, 0.01ether
69    #[arg(long, value_parser = parse_ether_value)]
70    pub value: Option<U256>,
71
72    /// Nonce for the transaction.
73    #[arg(long)]
74    pub nonce: Option<U64>,
75
76    /// Send a legacy transaction instead of an EIP1559 transaction.
77    ///
78    /// This is automatically enabled for common networks without EIP1559.
79    #[arg(long)]
80    pub legacy: bool,
81
82    /// Send a blob transaction using EIP-7594 (PeerDAS) format.
83    ///
84    /// Note: Use with `--eip4844` for the legacy EIP-4844 format.
85    #[arg(long, conflicts_with = "legacy")]
86    pub blob: bool,
87
88    /// Send a blob transaction using EIP-4844 (legacy) format instead of EIP-7594. Must be used
89    /// with `--blob`.
90    #[arg(long, conflicts_with = "legacy", requires = "blob")]
91    pub eip4844: bool,
92
93    /// Gas price for EIP-7594/EIP-4844 blob transaction.
94    #[arg(long, conflicts_with = "legacy", value_parser = parse_ether_value, env = "ETH_BLOB_GAS_PRICE", value_name = "BLOB_PRICE")]
95    pub blob_gas_price: Option<U256>,
96
97    /// EIP-7702 authorization list.
98    ///
99    /// Can be either a hex-encoded signed authorization or an address.
100    #[arg(long, conflicts_with_all = &["legacy", "blob"])]
101    pub auth: Vec<CliAuthorizationList>,
102
103    /// EIP-2930 access list.
104    ///
105    /// Accepts either a JSON-encoded access list or an empty value to create the access list
106    /// via an RPC call to `eth_createAccessList`. To retrieve only the access list portion, use
107    /// the `cast access-list` command.
108    #[arg(long, value_parser = parse_json::<AccessList>)]
109    pub access_list: Option<Option<AccessList>>,
110
111    #[command(flatten)]
112    pub tempo: TempoOpts,
113}
114
115impl TransactionOpts {
116    /// Applies gas, value, fee, and network-specific options to a transaction request.
117    pub fn apply<N: Network>(&self, tx: &mut N::TransactionRequest, legacy: bool)
118    where
119        N::TransactionRequest: FoundryTransactionBuilder<N>,
120    {
121        if let Some(gas_limit) = self.gas_limit {
122            tx.set_gas_limit(gas_limit.to());
123        }
124
125        if let Some(value) = self.value {
126            tx.set_value(value);
127        }
128
129        if let Some(gas_price) = self.gas_price {
130            if legacy {
131                tx.set_gas_price(gas_price.to());
132            } else {
133                tx.set_max_fee_per_gas(gas_price.to());
134            }
135        }
136
137        if !legacy && let Some(priority_fee) = self.priority_gas_price {
138            tx.set_max_priority_fee_per_gas(priority_fee.to());
139        }
140
141        if let Some(max_blob_fee) = self.blob_gas_price {
142            tx.set_max_fee_per_blob_gas(max_blob_fee.to())
143        }
144
145        // set network-specific options
146        self.tempo.apply::<N>(tx, self.nonce.map(|n| n.to()));
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn parse_priority_gas_tx_opts() {
156        let args: TransactionOpts =
157            TransactionOpts::parse_from(["foundry-cli", "--priority-gas-price", "100"]);
158        assert!(args.priority_gas_price.is_some());
159    }
160}