Skip to main content

cast/cmd/safe/
mod.rs

1use alloy_primitives::{Address, B256, Bytes, U256};
2use clap::{Parser, ValueEnum};
3use eyre::Result;
4use foundry_cli::{
5    opts::{RpcOpts, TransactionOpts},
6    utils::parse_ether_value,
7};
8use foundry_wallets::WalletOpts;
9
10mod contracts;
11mod delegates;
12mod deploy;
13mod execute;
14mod proposal;
15mod service;
16mod signing;
17mod simulate;
18mod transaction;
19
20use contracts::{
21    COMPATIBILITY_FALLBACK_HANDLER_V1_4_1, SAFE_PROXY_FACTORY_V1_4_1, SIMULATE_TX_ACCESSOR_V1_4_1,
22};
23pub use service::SafeServiceOpts;
24
25/// Safe transaction operations.
26#[derive(Debug, Parser)]
27pub enum SafeSubcommand {
28    /// Deploy a Safe account.
29    ///
30    /// Examples:
31    /// - cast safe create $OWNER --threshold 1 --rpc-url $RPC --ledger
32    /// - cast safe create $OWNER_1 $OWNER_2 $OWNER_3 --threshold 2 --rpc-url $RPC --account
33    ///   deployer
34    #[command(verbatim_doc_comment)]
35    Create {
36        /// Addresses that own the Safe.
37        #[arg(required = true, num_args = 1..)]
38        owners: Vec<Address>,
39
40        /// Number of owner signatures required. Defaults to all owners.
41        #[arg(long)]
42        threshold: Option<usize>,
43
44        /// CREATE2 salt nonce. Defaults to Safe Protocol Kit's chain-specific nonce.
45        #[arg(long)]
46        salt_nonce: Option<U256>,
47
48        /// Safe singleton address. Defaults to the canonical v1.4.1 deployment.
49        #[arg(long, conflicts_with = "l1")]
50        singleton: Option<Address>,
51
52        /// Use the L1 Safe singleton instead of SafeL2.
53        #[arg(long)]
54        l1: bool,
55
56        /// SafeProxyFactory address.
57        #[arg(long, default_value_t = SAFE_PROXY_FACTORY_V1_4_1)]
58        factory: Address,
59
60        /// CompatibilityFallbackHandler address. Pass the zero address to disable it.
61        #[arg(long, default_value_t = COMPATIBILITY_FALLBACK_HANDLER_V1_4_1)]
62        fallback_handler: Address,
63
64        /// Number of confirmations to wait for.
65        #[arg(long, default_value = "1")]
66        confirmations: u64,
67
68        /// Timeout for deployment confirmation, in seconds.
69        #[arg(long, env = "ETH_TIMEOUT")]
70        timeout: Option<u64>,
71
72        /// Polling interval for the deployment receipt, in seconds.
73        #[arg(long, env = "ETH_POLL_INTERVAL")]
74        poll_interval: Option<u64>,
75
76        #[command(flatten)]
77        rpc: Box<RpcOpts>,
78
79        #[command(flatten)]
80        wallet: Box<WalletOpts>,
81
82        #[command(flatten)]
83        tx: Box<TransactionOpts>,
84    },
85
86    /// Register a transaction-service delegate for a Safe owner.
87    AddDelegate {
88        /// Safe account address.
89        safe: Address,
90
91        /// Address allowed to propose transactions.
92        delegate: Address,
93
94        /// Human-readable delegate label.
95        #[arg(long)]
96        label: String,
97
98        #[command(flatten)]
99        service: Box<SafeServiceOpts>,
100
101        #[command(flatten)]
102        rpc: Box<RpcOpts>,
103
104        #[command(flatten)]
105        wallet: Box<WalletOpts>,
106    },
107
108    /// List transaction-service delegates registered for a Safe.
109    ListDelegates {
110        /// Safe account address.
111        safe: Address,
112
113        #[command(flatten)]
114        service: Box<SafeServiceOpts>,
115
116        #[command(flatten)]
117        rpc: Box<RpcOpts>,
118    },
119
120    /// Remove a transaction-service delegate for a Safe owner.
121    RemoveDelegate {
122        /// Safe account address.
123        safe: Address,
124
125        /// Delegate address to remove.
126        delegate: Address,
127
128        #[command(flatten)]
129        service: Box<SafeServiceOpts>,
130
131        #[command(flatten)]
132        rpc: Box<RpcOpts>,
133
134        #[command(flatten)]
135        wallet: Box<WalletOpts>,
136    },
137
138    /// Create, sign, and submit a Safe transaction proposal.
139    Propose {
140        /// Safe account address.
141        safe: Address,
142
143        /// Transaction target.
144        to: Address,
145
146        /// Function signature to call.
147        sig: Option<String>,
148
149        /// Function arguments.
150        #[arg(allow_negative_numbers = true)]
151        args: Vec<String>,
152
153        /// Raw calldata. Cannot be combined with a function signature or arguments.
154        #[arg(long, conflicts_with_all = ["sig", "args"])]
155        data: Option<Bytes>,
156
157        /// Native token value sent by the Safe.
158        #[arg(long, default_value = "0", value_parser = parse_ether_value)]
159        value: U256,
160
161        /// Safe operation type.
162        #[arg(long, value_enum, default_value_t = SafeOperation::Call)]
163        operation: SafeOperation,
164
165        /// Safe transaction gas.
166        #[arg(long, default_value = "0")]
167        safe_tx_gas: U256,
168
169        /// Base gas reimbursed by the Safe.
170        #[arg(long, default_value = "0")]
171        base_gas: U256,
172
173        /// Gas price reimbursed by the Safe.
174        #[arg(long, default_value = "0")]
175        gas_price: U256,
176
177        /// Token used for gas reimbursement.
178        #[arg(long, default_value_t = Address::ZERO)]
179        gas_token: Address,
180
181        /// Gas reimbursement receiver.
182        #[arg(long, default_value_t = Address::ZERO)]
183        refund_receiver: Address,
184
185        /// Safe nonce. Defaults to the next queued Transaction Service nonce.
186        #[arg(long)]
187        nonce: Option<U256>,
188
189        /// Optional origin shown by Safe clients.
190        #[arg(long)]
191        origin: Option<String>,
192
193        #[command(flatten)]
194        service: Box<SafeServiceOpts>,
195
196        #[command(flatten)]
197        rpc: Box<RpcOpts>,
198
199        #[command(flatten)]
200        wallet: Box<WalletOpts>,
201    },
202
203    /// Sign and submit a confirmation for a proposed Safe transaction.
204    Sign {
205        /// Safe account address.
206        safe: Address,
207
208        /// Safe transaction hash from the Transaction Service.
209        safe_tx_hash: B256,
210
211        #[command(flatten)]
212        service: Box<SafeServiceOpts>,
213
214        #[command(flatten)]
215        rpc: Box<RpcOpts>,
216
217        #[command(flatten)]
218        wallet: Box<WalletOpts>,
219    },
220
221    /// Simulate a proposed Safe transaction without requiring owner signatures.
222    ///
223    /// This simulates the inner CALL or DELEGATECALL in the Safe's context. It does not validate
224    /// the Safe nonce, owner signatures, threshold, or guard hooks. Reimbursed transactions
225    /// (`gasPrice > 0`) are rejected because SimulateTxAccessor does not enforce `safeTxGas`.
226    #[command(verbatim_doc_comment)]
227    Simulate {
228        /// Safe account address.
229        safe: Address,
230
231        /// Safe transaction hash from the Transaction Service.
232        safe_tx_hash: B256,
233
234        /// Address that will execute the Safe transaction. Used as the simulation's tx.origin.
235        #[arg(long, env = "ETH_FROM", value_name = "ADDRESS")]
236        from: Address,
237
238        /// SimulateTxAccessor address.
239        #[arg(long, default_value_t = SIMULATE_TX_ACCESSOR_V1_4_1)]
240        accessor: Address,
241
242        #[command(flatten)]
243        service: Box<SafeServiceOpts>,
244
245        #[command(flatten)]
246        rpc: Box<RpcOpts>,
247    },
248
249    /// Execute a confirmed Safe transaction onchain.
250    Execute {
251        /// Safe account address.
252        safe: Address,
253
254        /// Safe transaction hash from the Transaction Service.
255        safe_tx_hash: B256,
256
257        /// Number of confirmations to wait for.
258        #[arg(long, default_value = "1")]
259        confirmations: u64,
260
261        /// Timeout for execution confirmation, in seconds.
262        #[arg(long, env = "ETH_TIMEOUT")]
263        timeout: Option<u64>,
264
265        /// Polling interval for the execution receipt, in seconds.
266        #[arg(long, env = "ETH_POLL_INTERVAL")]
267        poll_interval: Option<u64>,
268
269        #[command(flatten)]
270        service: Box<SafeServiceOpts>,
271
272        #[command(flatten)]
273        rpc: Box<RpcOpts>,
274
275        #[command(flatten)]
276        wallet: Box<WalletOpts>,
277
278        #[command(flatten)]
279        tx: Box<TransactionOpts>,
280    },
281}
282
283#[derive(Clone, Copy, Debug, Default, ValueEnum)]
284#[repr(u8)]
285pub enum SafeOperation {
286    #[default]
287    Call = 0,
288    DelegateCall = 1,
289}
290
291impl SafeSubcommand {
292    pub async fn run(self) -> Result<()> {
293        match self {
294            Self::Create {
295                owners,
296                threshold,
297                salt_nonce,
298                singleton,
299                l1,
300                factory,
301                fallback_handler,
302                confirmations,
303                timeout,
304                poll_interval,
305                rpc,
306                wallet,
307                tx,
308            } => {
309                deploy::run(
310                    owners,
311                    threshold,
312                    salt_nonce,
313                    singleton,
314                    l1,
315                    factory,
316                    fallback_handler,
317                    confirmations,
318                    timeout,
319                    poll_interval,
320                    *rpc,
321                    *wallet,
322                    *tx,
323                )
324                .await?;
325            }
326            Self::AddDelegate { safe, delegate, label, service, rpc, wallet } => {
327                delegates::add(safe, delegate, label, *service, *rpc, *wallet).await?;
328            }
329            Self::ListDelegates { safe, service, rpc } => {
330                delegates::list(safe, *service, *rpc).await?;
331            }
332            Self::RemoveDelegate { safe, delegate, service, rpc, wallet } => {
333                delegates::remove(safe, delegate, *service, *rpc, *wallet).await?;
334            }
335            Self::Propose {
336                safe,
337                to,
338                sig,
339                args,
340                data,
341                value,
342                operation,
343                safe_tx_gas,
344                base_gas,
345                gas_price,
346                gas_token,
347                refund_receiver,
348                nonce,
349                origin,
350                service,
351                rpc,
352                wallet,
353            } => {
354                proposal::propose(
355                    safe,
356                    to,
357                    sig,
358                    args,
359                    data,
360                    value,
361                    operation,
362                    safe_tx_gas,
363                    base_gas,
364                    gas_price,
365                    gas_token,
366                    refund_receiver,
367                    nonce,
368                    origin,
369                    *service,
370                    *rpc,
371                    *wallet,
372                )
373                .await?;
374            }
375            Self::Sign { safe, safe_tx_hash, service, rpc, wallet } => {
376                proposal::sign(safe, safe_tx_hash, *service, *rpc, *wallet).await?;
377            }
378            Self::Simulate { safe, safe_tx_hash, from, accessor, service, rpc } => {
379                simulate::run(safe, safe_tx_hash, from, accessor, *service, *rpc).await?;
380            }
381            Self::Execute {
382                safe,
383                safe_tx_hash,
384                confirmations,
385                timeout,
386                poll_interval,
387                service,
388                rpc,
389                wallet,
390                tx,
391            } => {
392                execute::run(
393                    safe,
394                    safe_tx_hash,
395                    confirmations,
396                    timeout,
397                    poll_interval,
398                    *service,
399                    *rpc,
400                    *wallet,
401                    *tx,
402                )
403                .await?;
404            }
405        }
406        Ok(())
407    }
408}