near-cli-rs 0.26.2

human-friendly console utility that helps to interact with NEAR Protocol from command line.
Documentation
use color_eyre::eyre::{Context, eyre};
use near_primitives::action::Action;
use serde::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as};

#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ActionCall {
    method_name: String,
    #[serde_as(as = "Base64")]
    args: Vec<u8>,
    deposit: near_token::NearToken,
    gas: near_gas::NearGas,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TransferArgs {
    #[serde(default)]
    token_id: String,
    receiver_id: near_primitives::types::AccountId,
    amount: crate::types::near_token::NearToken,
    msg: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FunctionCallArgs {
    receiver_id: near_primitives::types::AccountId,
    actions: Vec<ActionCall>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
/// Enum for DAO proposal arguments
///
/// Directly translates to `{ "TransferKindName": TransferArguments }`
pub enum ProposalKind {
    Transfer(TransferArgs),
    FunctionCall(FunctionCallArgs),
}

impl TryFrom<&crate::commands::PrepopulatedTransaction> for ProposalKind {
    type Error = color_eyre::eyre::Error;

    fn try_from(
        transaction: &crate::commands::PrepopulatedTransaction,
    ) -> Result<Self, Self::Error> {
        let Some(first_action) = transaction.actions.first() else {
            return Err(eyre!("No actions were found in transaction!"));
        };

        match first_action {
            Action::Transfer(transfer_action) => {
                if transaction.actions.len() > 1 {
                    Err(eyre!("Batch transfers are not supported for DAO proposals"))
                } else {
                    Ok(ProposalKind::Transfer(TransferArgs {
                        token_id: String::new(),
                        receiver_id: transaction.receiver_id.clone(),
                        amount: transfer_action.deposit.into(),
                        msg: None,
                    }))
                }
            }
            Action::FunctionCall(_) => {
                let action_calls: Vec<_> = transaction
                    .actions
                    .iter()
                    .filter_map(|action| {
                        if let Action::FunctionCall(function_call_action) = action {
                            Some(ActionCall {
                                method_name: function_call_action.method_name.clone(),
                                args: function_call_action.args.clone(),
                                deposit: function_call_action.deposit,
                                gas: near_gas::NearGas::from_gas(function_call_action.gas.as_gas()),
                            })
                        } else {
                            None
                        }
                    })
                    .collect();

                if action_calls.len() != transaction.actions.len() {
                    Err(eyre!(
                        "Mixed action types are not supported for DAO proposals"
                    ))
                } else {
                    Ok(ProposalKind::FunctionCall(FunctionCallArgs {
                        receiver_id: transaction.receiver_id.clone(),
                        actions: action_calls,
                    }))
                }
            }
            _action => Err(eyre!(
                "Passed action type is not supported for DAO proposal",
            )),
        }
    }
}

impl ProposalKind {
    pub fn try_to_mpc_sign_request(
        self,
        network_config: &crate::config::NetworkConfig,
    ) -> Result<
        crate::transaction_signature_options::sign_with_mpc::mpc_sign_request::MpcSignRequest,
        color_eyre::eyre::Error,
    > {
        match self {
            ProposalKind::FunctionCall(fc_args) => {
                if fc_args.receiver_id != network_config.get_mpc_contract_account_id()? {
                    return Err(color_eyre::eyre::eyre!(
                        "ReceiverId of Function Call proposal doesn't match MPC contract AccountId in selected NetworkConfig!"
                    ));
                }

                let mpc_sign_action = fc_args.actions.first().ok_or_else(|| {
                    color_eyre::eyre::eyre!(
                        "Function Call proposal has no actions, but MPC sign requires at least one"
                    )
                })?;

                if mpc_sign_action.method_name != "sign" {
                    return Err(color_eyre::eyre::eyre!(
                        "Method name for MPC sign Function Call is not \"sign\""
                    ));
                }

                serde_json::from_slice(&mpc_sign_action.args).wrap_err_with(|| {
                    format!(
                        "{}{}",
                        "Failed to parse MPC sign request from Function Call action arguments.\n",
                        "Expected SignRequest structure with 'request' field containing
  payload_v2, path, and domain_id."
                    )
                })
            }
            _ => Err(eyre!(
                "Proposal Kind is not FunctionCall and cannot be turned to MpcSignRequest!"
            )),
        }
    }
}