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#[derive(Debug, Parser)]
27pub enum SafeSubcommand {
28 #[command(verbatim_doc_comment)]
35 Create {
36 #[arg(required = true, num_args = 1..)]
38 owners: Vec<Address>,
39
40 #[arg(long)]
42 threshold: Option<usize>,
43
44 #[arg(long)]
46 salt_nonce: Option<U256>,
47
48 #[arg(long, conflicts_with = "l1")]
50 singleton: Option<Address>,
51
52 #[arg(long)]
54 l1: bool,
55
56 #[arg(long, default_value_t = SAFE_PROXY_FACTORY_V1_4_1)]
58 factory: Address,
59
60 #[arg(long, default_value_t = COMPATIBILITY_FALLBACK_HANDLER_V1_4_1)]
62 fallback_handler: Address,
63
64 #[arg(long, default_value = "1")]
66 confirmations: u64,
67
68 #[arg(long, env = "ETH_TIMEOUT")]
70 timeout: Option<u64>,
71
72 #[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 AddDelegate {
88 safe: Address,
90
91 delegate: Address,
93
94 #[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 ListDelegates {
110 safe: Address,
112
113 #[command(flatten)]
114 service: Box<SafeServiceOpts>,
115
116 #[command(flatten)]
117 rpc: Box<RpcOpts>,
118 },
119
120 RemoveDelegate {
122 safe: Address,
124
125 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 Propose {
140 safe: Address,
142
143 to: Address,
145
146 sig: Option<String>,
148
149 #[arg(allow_negative_numbers = true)]
151 args: Vec<String>,
152
153 #[arg(long, conflicts_with_all = ["sig", "args"])]
155 data: Option<Bytes>,
156
157 #[arg(long, default_value = "0", value_parser = parse_ether_value)]
159 value: U256,
160
161 #[arg(long, value_enum, default_value_t = SafeOperation::Call)]
163 operation: SafeOperation,
164
165 #[arg(long, default_value = "0")]
167 safe_tx_gas: U256,
168
169 #[arg(long, default_value = "0")]
171 base_gas: U256,
172
173 #[arg(long, default_value = "0")]
175 gas_price: U256,
176
177 #[arg(long, default_value_t = Address::ZERO)]
179 gas_token: Address,
180
181 #[arg(long, default_value_t = Address::ZERO)]
183 refund_receiver: Address,
184
185 #[arg(long)]
187 nonce: Option<U256>,
188
189 #[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 {
205 safe: Address,
207
208 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 #[command(verbatim_doc_comment)]
227 Simulate {
228 safe: Address,
230
231 safe_tx_hash: B256,
233
234 #[arg(long, env = "ETH_FROM", value_name = "ADDRESS")]
236 from: Address,
237
238 #[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 {
251 safe: Address,
253
254 safe_tx_hash: B256,
256
257 #[arg(long, default_value = "1")]
259 confirmations: u64,
260
261 #[arg(long, env = "ETH_TIMEOUT")]
263 timeout: Option<u64>,
264
265 #[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}