cast/cmd/safe/
proposal.rs1use super::{
2 SafeOperation,
3 contracts::ISafe,
4 service::{SafeServiceOpts, SafeTransaction},
5 signing::sign_safe_hash,
6};
7use alloy_network::Ethereum;
8use alloy_primitives::{Address, B256, Bytes, U256};
9use alloy_provider::Provider;
10use alloy_signer::Signer;
11use eyre::Result;
12use foundry_cli::{json::print_scalar, opts::RpcOpts, utils::LoadConfig};
13use foundry_common::{
14 abi::{encode_function_args, get_func},
15 provider::ProviderBuilder,
16};
17use foundry_wallets::WalletOpts;
18use reqwest::Method;
19use serde_json::json;
20
21#[allow(clippy::too_many_arguments)]
22pub(super) async fn propose(
23 safe: Address,
24 to: Address,
25 sig: Option<String>,
26 args: Vec<String>,
27 data: Option<Bytes>,
28 value: U256,
29 operation: SafeOperation,
30 safe_tx_gas: U256,
31 base_gas: U256,
32 gas_price: U256,
33 gas_token: Address,
34 refund_receiver: Address,
35 nonce: Option<U256>,
36 origin: Option<String>,
37 service: SafeServiceOpts,
38 rpc: RpcOpts,
39 wallet: WalletOpts,
40) -> Result<()> {
41 let data = match (data, sig) {
42 (Some(data), None) => data,
43 (None, Some(sig)) => encode_function_args(&get_func(&sig)?, &args)?.into(),
44 (None, None) => Bytes::new(),
45 (Some(_), Some(_)) => unreachable!("enforced by clap"),
46 };
47 let config = rpc.load_config()?;
48 let provider = ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
49 let chain_id = provider.get_chain_id().await?;
50 let onchain_nonce = ISafe::new(safe, &provider).nonce().call().await?;
51 let nonce = match nonce {
52 Some(nonce) => nonce,
53 None => service.next_nonce(chain_id, safe, onchain_nonce).await?,
54 };
55 let mut transaction = SafeTransaction {
56 safe,
57 to,
58 value: value.to_string(),
59 data,
60 operation: operation as u8,
61 safe_tx_gas: safe_tx_gas.to_string(),
62 base_gas: base_gas.to_string(),
63 gas_price: gas_price.to_string(),
64 gas_token,
65 refund_receiver,
66 nonce: nonce.to_string(),
67 safe_tx_hash: B256::ZERO,
68 confirmations: Vec::new(),
69 is_executed: false,
70 transaction_hash: None,
71 };
72 transaction.safe_tx_hash = transaction.calculate_hash(&provider).await?;
73 transaction.show_transaction_summary()?;
74 let signer = wallet.signer().await?;
75 let signature = sign_safe_hash(&signer, transaction.safe_tx_hash).await?;
76 let url = service.endpoint(
77 chain_id,
78 &format!("v2/safes/{}/multisig-transactions/", transaction.safe.to_checksum(None)),
79 )?;
80 let body = transaction.proposal_body(signer.address(), signature, origin);
81 service.empty_response(service.request(Method::POST, url).json(&body)).await?;
82 print_scalar(transaction.safe_tx_hash)?;
83 Ok(())
84}
85
86pub(super) async fn sign(
87 safe: Address,
88 safe_tx_hash: B256,
89 service: SafeServiceOpts,
90 rpc: RpcOpts,
91 wallet: WalletOpts,
92) -> Result<()> {
93 let config = rpc.load_config()?;
94 let provider = ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
95 let chain_id = provider.get_chain_id().await?;
96 let transaction = service.get_transaction(chain_id, "v1", safe_tx_hash).await?;
97 transaction.verify_hash(safe, &provider).await?;
98 transaction.show_transaction_summary()?;
99 let signer = wallet.signer().await?;
100 let signature = sign_safe_hash(&signer, safe_tx_hash).await?;
101 let url = service
102 .endpoint(chain_id, &format!("v1/multisig-transactions/{safe_tx_hash}/confirmations/"))?;
103 service
104 .empty_response(service.request(Method::POST, url).json(&json!({
105 "signature": signature,
106 })))
107 .await?;
108 print_scalar(signature)?;
109 Ok(())
110}