use color_eyre::owo_colors::OwoColorize;
use inquire::Select;
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = crate::GlobalContext)]
#[interactive_clap(output_context = DeleteAccountContext)]
pub struct DeleteAccount {
#[interactive_clap(skip_default_input_arg)]
account_id: crate::types::account_id::AccountId,
#[interactive_clap(named_arg)]
beneficiary: BeneficiaryAccount,
}
#[derive(Debug, Clone)]
pub struct DeleteAccountContext {
global_context: crate::GlobalContext,
account_id: near_primitives::types::AccountId,
}
impl DeleteAccountContext {
pub fn from_previous_context(
previous_context: crate::GlobalContext,
scope: &<DeleteAccount as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
Ok(Self {
global_context: previous_context,
account_id: scope.account_id.clone().into(),
})
}
}
impl DeleteAccount {
pub fn input_account_id(
context: &crate::GlobalContext,
) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
crate::common::input_signer_account_id_from_used_account_list(
&context.config.credentials_home_dir,
"What Account ID to be deleted?",
)
}
}
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = DeleteAccountContext)]
#[interactive_clap(output_context = BeneficiaryAccountContext)]
pub struct BeneficiaryAccount {
#[interactive_clap(skip_default_input_arg)]
beneficiary_account_id: crate::types::account_id::AccountId,
#[interactive_clap(named_arg)]
network_config: crate::network_for_transaction::NetworkForTransactionArgs,
}
#[derive(Debug, Clone)]
pub struct BeneficiaryAccountContext {
global_context: crate::GlobalContext,
account_id: near_primitives::types::AccountId,
beneficiary_account_id: near_primitives::types::AccountId,
}
impl BeneficiaryAccountContext {
pub fn from_previous_context(
previous_context: DeleteAccountContext,
scope: &<BeneficiaryAccount as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
Ok(Self {
global_context: previous_context.global_context,
account_id: previous_context.account_id,
beneficiary_account_id: scope.beneficiary_account_id.clone().into(),
})
}
}
impl From<BeneficiaryAccountContext> for crate::commands::ActionContext {
fn from(item: BeneficiaryAccountContext) -> Self {
let get_prepopulated_transaction_after_getting_network_callback: crate::commands::GetPrepopulatedTransactionAfterGettingNetworkCallback =
std::sync::Arc::new({
let account_id = item.account_id.clone();
move |_network_config| {
Ok(crate::commands::PrepopulatedTransaction {
signer_id: account_id.clone(),
receiver_id: account_id.clone(),
actions: vec![near_primitives::transaction::Action::DeleteAccount(
near_primitives::transaction::DeleteAccountAction {
beneficiary_id: item.beneficiary_account_id.clone(),
},
)],
})
}
});
Self {
global_context: item.global_context,
interacting_with_account_ids: vec![item.account_id],
get_prepopulated_transaction_after_getting_network_callback,
on_before_signing_callback: std::sync::Arc::new(
|_prepopulated_unsigned_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: std::sync::Arc::new(
|_outcome_view, _network_config| Ok(()),
),
sign_as_delegate_action: false,
on_sending_delegate_action_callback: None,
}
}
}
impl BeneficiaryAccount {
pub fn input_beneficiary_account_id(
context: &DeleteAccountContext,
) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
loop {
let beneficiary_account_id = if let Some(account_id) =
crate::common::input_non_signer_account_id_from_used_account_list(
&context.global_context.config.credentials_home_dir,
"What is the beneficiary account ID?",
)? {
account_id
} else {
return Ok(None);
};
if beneficiary_account_id.0 == context.account_id {
tracing::warn!("{}", "You have selected a beneficiary account ID that will now be deleted. This will result in the loss of your funds. So make your choice again.".red());
continue;
}
if context.global_context.offline {
return Ok(Some(beneficiary_account_id));
}
#[derive(derive_more::Display)]
enum ConfirmOptions {
#[display(
"Yes, I want to check if account <{account_id}> exists. (It is free of charge, and only requires Internet access)"
)]
Yes {
account_id: crate::types::account_id::AccountId,
},
#[display("No, I know this account exists and want to continue.")]
No,
}
let select_choose_input =
Select::new("Do you want to check the existence of the specified account so that you don't lose tokens?",
vec![ConfirmOptions::Yes{account_id: beneficiary_account_id.clone()}, ConfirmOptions::No],
)
.prompt()?;
if let ConfirmOptions::Yes { account_id } = select_choose_input {
let network_where_account_exist =
match crate::common::find_network_where_account_exist(
&context.global_context,
account_id.clone().into(),
) {
Ok(network_config) => network_config,
Err(err) => {
tracing::warn!("{}{}",
"Cannot verify beneficiary. Proceeding may result in total loss of NEAR tokens of the deleting account.".red(),
crate::common::indent_payload(&format!("\n{}{}",
format!("{err}").red(),
"\nIt is currently possible to continue deleting an account offline.\nYou can sign and send the created transaction later.\n "
.yellow()
))
);
return Ok(Some(account_id));
}
};
if network_where_account_exist.is_none() {
tracing::warn!("{}",
format!(
"Heads up! You will lose remaining NEAR tokens on the account you delete if you specify the account <{}> as the beneficiary as it does not exist on [{}] networks.",
account_id,
context.global_context.config.network_names().join(", ")
).red()
);
if !crate::common::ask_if_different_account_id_wanted()? {
return Ok(Some(account_id));
}
} else {
return Ok(Some(account_id));
};
} else {
return Ok(Some(beneficiary_account_id));
};
}
}
}