1use super::{
2 SafeOperation,
3 contracts::ISafe,
4 rpc_provider,
5 service::{SafeServiceOpts, SafeTransaction},
6 signing::sign_safe_hash,
7};
8use alloy_primitives::{Address, B256, Bytes, U256};
9use alloy_signer::Signer;
10use clap::Args;
11use eyre::Result;
12use foundry_cli::{json::print_scalar, opts::RpcOpts, utils::parse_ether_value};
13use foundry_common::abi::{encode_function_args, get_func};
14use foundry_wallets::WalletOpts;
15use reqwest::Method;
16use serde_json::json;
17
18#[derive(Args, Debug)]
20pub struct ProposeArgs {
21 safe: Address,
23
24 to: Address,
26
27 sig: Option<String>,
29
30 #[arg(allow_negative_numbers = true)]
32 args: Vec<String>,
33
34 #[arg(long, conflicts_with_all = ["sig", "args"])]
36 data: Option<Bytes>,
37
38 #[arg(long, default_value = "0", value_parser = parse_ether_value)]
40 value: U256,
41
42 #[arg(long, value_enum, default_value_t = SafeOperation::Call)]
44 operation: SafeOperation,
45
46 #[arg(long, default_value = "0")]
48 safe_tx_gas: U256,
49
50 #[arg(long, default_value = "0")]
52 base_gas: U256,
53
54 #[arg(long, default_value = "0")]
56 gas_price: U256,
57
58 #[arg(long, default_value_t = Address::ZERO)]
60 gas_token: Address,
61
62 #[arg(long, default_value_t = Address::ZERO)]
64 refund_receiver: Address,
65
66 #[arg(long)]
68 nonce: Option<U256>,
69
70 #[arg(long)]
72 origin: Option<String>,
73
74 #[command(flatten)]
75 service: Box<SafeServiceOpts>,
76
77 #[command(flatten)]
78 rpc: Box<RpcOpts>,
79
80 #[command(flatten)]
81 wallet: Box<WalletOpts>,
82}
83
84impl ProposeArgs {
85 pub(super) async fn run(self) -> Result<()> {
86 let Self {
87 safe,
88 to,
89 sig,
90 args,
91 data,
92 value,
93 operation,
94 safe_tx_gas,
95 base_gas,
96 gas_price,
97 gas_token,
98 refund_receiver,
99 nonce,
100 origin,
101 service,
102 rpc,
103 wallet,
104 } = self;
105 let data = match (data, sig) {
106 (Some(data), None) => data,
107 (None, Some(sig)) => encode_function_args(&get_func(&sig)?, &args)?.into(),
108 (None, None) => Bytes::new(),
109 (Some(_), Some(_)) => unreachable!("enforced by clap"),
110 };
111 let (provider, chain_id) = rpc_provider(&rpc).await?;
112 let nonce = match nonce {
113 Some(nonce) => nonce,
114 None => {
115 let onchain_nonce = ISafe::new(safe, &provider).nonce().call().await?;
116 service.next_nonce(chain_id, safe, onchain_nonce).await?
117 }
118 };
119 let mut transaction = SafeTransaction {
120 safe,
121 to,
122 value: value.to_string(),
123 data,
124 operation: operation as u8,
125 safe_tx_gas: safe_tx_gas.to_string(),
126 base_gas: base_gas.to_string(),
127 gas_price: gas_price.to_string(),
128 gas_token,
129 refund_receiver,
130 nonce: nonce.to_string(),
131 safe_tx_hash: B256::ZERO,
132 confirmations: Vec::new(),
133 is_executed: false,
134 transaction_hash: None,
135 };
136 transaction.safe_tx_hash = transaction.calculate_hash(&provider).await?;
137 transaction.show_transaction_summary()?;
138 let signer = wallet.signer().await?;
139 let signature = sign_safe_hash(&signer, transaction.safe_tx_hash).await?;
140 let url = service.endpoint(
141 chain_id,
142 &format!("v2/safes/{}/multisig-transactions/", transaction.safe.to_checksum(None)),
143 )?;
144 let body = transaction.proposal_body(signer.address(), signature, origin);
145 service.empty_response(service.request(Method::POST, url).json(&body)).await?;
146 print_scalar(transaction.safe_tx_hash)
147 }
148}
149
150#[derive(Args, Debug)]
152pub struct SignArgs {
153 safe: Address,
155
156 safe_tx_hash: B256,
158
159 #[command(flatten)]
160 service: Box<SafeServiceOpts>,
161
162 #[command(flatten)]
163 rpc: Box<RpcOpts>,
164
165 #[command(flatten)]
166 wallet: Box<WalletOpts>,
167}
168
169impl SignArgs {
170 pub(super) async fn run(self) -> Result<()> {
171 let Self { safe, safe_tx_hash, service, rpc, wallet } = self;
172 let (provider, chain_id) = rpc_provider(&rpc).await?;
173 let transaction = service.get_transaction(chain_id, "v1", safe_tx_hash).await?;
174 transaction.verify_hash(safe, &provider).await?;
175 transaction.show_transaction_summary()?;
176 let signature = sign_safe_hash(&wallet.signer().await?, safe_tx_hash).await?;
177 let url = service.endpoint(
178 chain_id,
179 &format!("v1/multisig-transactions/{safe_tx_hash}/confirmations/"),
180 )?;
181 let body = json!({ "signature": signature });
182 service.empty_response(service.request(Method::POST, url).json(&body)).await?;
183 print_scalar(signature)
184 }
185}