near-cli-rs 0.25.0

human-friendly console utility that helps to interact with NEAR Protocol from command line.
Documentation
use inquire::CustomType;
use inquire::Text;

pub mod dao_kind_arguments;
pub mod dao_sign_with;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = crate::commands::TransactionContext)]
#[interactive_clap(output_context = DaoProposalContext)]
pub struct DaoProposal {
    #[interactive_clap(skip_default_input_arg)]
    /// What is dao account ID?
    dao_account_id: crate::types::account_id::AccountId,
    #[interactive_clap(subargs)]
    /// Proposal function params
    proposal_arguments: DaoProposalArguments,
}

#[derive(Clone)]
pub struct DaoProposalContext {
    global_context: crate::GlobalContext,
    network_config: crate::config::NetworkConfig,
    on_after_sending_transaction_callback:
        crate::transaction_signature_options::OnAfterSendingTransactionCallback,
    dao_account_id: near_primitives::types::AccountId,
    receiver_id: near_primitives::types::AccountId,
    proposal_kind: dao_kind_arguments::ProposalKind,
}

impl DaoProposalContext {
    pub fn from_previous_context(
        previous_context: crate::commands::TransactionContext,
        scope: &<DaoProposal as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
    ) -> color_eyre::eyre::Result<Self> {
        let proposal_kind =
            dao_kind_arguments::ProposalKind::try_from(&previous_context.prepopulated_transaction)?;

        let on_after_sending_transaction_callback = if proposal_kind
            .clone()
            .try_to_mpc_sign_request(&previous_context.network_config)
            .is_ok()
        {
            previous_context.on_after_sending_transaction_callback
        } else {
            std::sync::Arc::new(
                |_final_outcome_view: &near_primitives::views::FinalExecutionOutcomeView,
                 _network_config: &crate::config::NetworkConfig| Ok(()),
            )
        };

        Ok(Self {
            global_context: previous_context.global_context,
            network_config: previous_context.network_config,
            on_after_sending_transaction_callback,
            dao_account_id: scope.dao_account_id.clone().into(),
            receiver_id: previous_context.prepopulated_transaction.signer_id.clone(),
            proposal_kind,
        })
    }
}

impl DaoProposal {
    pub fn input_dao_account_id(
        context: &crate::commands::TransactionContext,
    ) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
        crate::common::input_signer_account_id_from_used_account_list(
            &context.global_context.config.credentials_home_dir,
            "What is the DAO member account ID?",
        )
    }
}

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = DaoProposalContext)]
#[interactive_clap(output_context = DaoProposalArgumentsContext)]
pub struct DaoProposalArguments {
    #[interactive_clap(skip_default_input_arg)]
    /// Enter proposal description:
    proposal_description: String,
    #[interactive_clap(named_arg)]
    /// Enter gas amount for DAO proposal:
    prepaid_gas: PrepaidGas,
}

#[derive(Clone)]
pub struct DaoProposalArgumentsContext {
    global_context: crate::GlobalContext,
    network_config: crate::config::NetworkConfig,
    on_after_sending_transaction_callback:
        crate::transaction_signature_options::OnAfterSendingTransactionCallback,
    dao_account_id: near_primitives::types::AccountId,
    receiver_id: near_primitives::types::AccountId,
    proposal_args: Vec<u8>,
}

impl DaoProposalArgumentsContext {
    pub fn from_previous_context(
        previous_context: DaoProposalContext,
        scope: &<DaoProposalArguments as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
    ) -> color_eyre::eyre::Result<Self> {
        let proposal_args = serde_json::to_vec(&serde_json::json!({
            "proposal": {
                "description": scope.proposal_description,
                "kind": previous_context.proposal_kind,
            },
        }))?;

        Ok(Self {
            global_context: previous_context.global_context,
            network_config: previous_context.network_config,
            on_after_sending_transaction_callback: previous_context
                .on_after_sending_transaction_callback,
            dao_account_id: previous_context.dao_account_id,
            receiver_id: previous_context.receiver_id,
            proposal_args,
        })
    }
}

impl DaoProposalArguments {
    pub fn input_proposal_description(
        _context: &DaoProposalContext,
    ) -> color_eyre::eyre::Result<Option<String>> {
        Ok(Some(
            Text::new("Enter a description for the DAO proposal:").prompt()?,
        ))
    }
}

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = DaoProposalArgumentsContext)]
#[interactive_clap(output_context = PrepaidGasContext)]
pub struct PrepaidGas {
    #[interactive_clap(skip_default_input_arg)]
    /// Enter gas amount for DAO proposal:
    gas: crate::common::NearGas,
    #[interactive_clap(named_arg)]
    /// Enter deposit for DAO proposal:
    attached_deposit: Deposit,
}

#[derive(Clone)]
pub struct PrepaidGasContext {
    global_context: crate::GlobalContext,
    network_config: crate::config::NetworkConfig,
    on_after_sending_transaction_callback:
        crate::transaction_signature_options::OnAfterSendingTransactionCallback,
    dao_account_id: near_primitives::types::AccountId,
    receiver_id: near_primitives::types::AccountId,
    proposal_args: Vec<u8>,
    gas: crate::common::NearGas,
}

impl PrepaidGasContext {
    pub fn from_previous_context(
        previous_context: DaoProposalArgumentsContext,
        scope: &<PrepaidGas as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
    ) -> color_eyre::eyre::Result<Self> {
        Ok(Self {
            global_context: previous_context.global_context,
            network_config: previous_context.network_config,
            on_after_sending_transaction_callback: previous_context
                .on_after_sending_transaction_callback,
            dao_account_id: previous_context.dao_account_id,
            receiver_id: previous_context.receiver_id,
            proposal_args: previous_context.proposal_args,
            gas: scope.gas,
        })
    }
}

impl PrepaidGas {
    pub fn input_gas(
        _context: &DaoProposalArgumentsContext,
    ) -> color_eyre::eyre::Result<Option<crate::common::NearGas>> {
        Ok(Some(
            CustomType::new(
                "What is the gas limit for adding a DAO proposal (if you're not sure, keep 10 Tgas)?",
            )
            .with_starting_input("10 Tgas")
            .with_validator(move |gas: &crate::common::NearGas| {
                if gas > &near_gas::NearGas::from_tgas(1000) {
                    Ok(inquire::validator::Validation::Invalid(
                        inquire::validator::ErrorMessage::Custom(
                            "You need to enter a value of no more than 1000 TeraGas".to_string(),
                        ),
                    ))
                } else {
                    Ok(inquire::validator::Validation::Valid)
                }
            })
            .prompt()?,
        ))
    }
}

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context=PrepaidGasContext)]
#[interactive_clap(output_context=DepositContext)]
pub struct Deposit {
    #[interactive_clap(skip_default_input_arg)]
    /// Enter deposit for DAO proposal:
    deposit: crate::types::near_token::NearToken,
    #[interactive_clap(subcommand)]
    transaction_signature_options: dao_sign_with::DaoSignWith,
}

#[derive(Clone)]
pub struct DepositContext {
    global_context: crate::GlobalContext,
    network_config: crate::config::NetworkConfig,
    on_after_sending_transaction_callback:
        crate::transaction_signature_options::OnAfterSendingTransactionCallback,
    dao_account_id: near_primitives::types::AccountId,
    receiver_id: near_primitives::types::AccountId,
    proposal_args: Vec<u8>,
    gas: crate::common::NearGas,
    deposit: crate::types::near_token::NearToken,
}

impl DepositContext {
    pub fn from_previous_context(
        previous_context: PrepaidGasContext,
        scope: &<Deposit as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
    ) -> color_eyre::eyre::Result<Self> {
        Ok(Self {
            global_context: previous_context.global_context,
            network_config: previous_context.network_config,
            on_after_sending_transaction_callback: previous_context
                .on_after_sending_transaction_callback,
            dao_account_id: previous_context.dao_account_id,
            receiver_id: previous_context.receiver_id,
            proposal_args: previous_context.proposal_args,
            gas: previous_context.gas,
            deposit: scope.deposit,
        })
    }
}

impl Deposit {
    pub fn input_deposit(
        _context: &PrepaidGasContext,
    ) -> color_eyre::eyre::Result<Option<crate::types::near_token::NearToken>> {
        Ok(Some(
            CustomType::new("Enter deposit for adding a DAO proposal:")
                .with_starting_input("0 NEAR")
                .prompt()?,
        ))
    }
}

impl From<DepositContext> for crate::commands::TransactionContext {
    fn from(item: DepositContext) -> Self {
        let sign_as_delegate_action = item.network_config.meta_transaction_relayer_url.is_some();
        let new_prepopulated_transaction = crate::commands::PrepopulatedTransaction {
            signer_id: item.dao_account_id,
            receiver_id: item.receiver_id,
            actions: vec![near_primitives::transaction::Action::FunctionCall(
                Box::new(near_primitives::transaction::FunctionCallAction {
                    method_name: "add_proposal".to_string(),
                    args: item.proposal_args.clone(),
                    gas: near_primitives::gas::Gas::from_gas(item.gas.as_gas()),
                    deposit: item.deposit.into(),
                }),
            )],
        };

        tracing::info!(
            "{}{}",
            "Unsigned DAO proposal",
            crate::common::indent_payload(&crate::common::print_unsigned_transaction(
                &new_prepopulated_transaction,
            ))
        );

        Self {
            global_context: item.global_context,
            network_config: item.network_config,
            prepopulated_transaction: new_prepopulated_transaction,
            on_before_signing_callback: std::sync::Arc::new(
                |_prepopulated_unsigned_transaction, _network_config| Ok(()),
            ),
            on_after_signing_callback: std::sync::Arc::new(
                |_signed_transaction, _network_config| Ok(()),
            ),
            on_before_sending_transaction_callback: std::sync::Arc::new(
                |_signed_transaction, _network_config| Ok(String::new()),
            ),
            on_after_sending_transaction_callback: item.on_after_sending_transaction_callback,
            sign_as_delegate_action,
        }
    }
}