Skip to main content

cast/cmd/safe/
proposal.rs

1use 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}