diff --git a/docs/GUIDE.en.md b/docs/GUIDE.en.md index 3ae80d0fb..7a21a5c19 100644 --- a/docs/GUIDE.en.md +++ b/docs/GUIDE.en.md @@ -97,6 +97,7 @@ View account details ([View properties for an account](#view-account-summary---v - [list-keys](#list-keys---View-a-list-of-access-keys-of-an-account) - [add-key](#add-key---Add-an-access-key-to-an-account) - [delete-key](#delete-key---Delete-an-access-key-from-an-account) +- [manage-storage-deposit](#manage-storage-deposit---Storage-management-deposit-withdrawal-balance-review) #### view-account-summary - View properties for an account @@ -314,6 +315,72 @@ The data for the access key is saved in macOS Keychain +#### export-account - Export existing account + +- [using-web-wallet](#using-web-wallet---Export-existing-account-using-NEAR-Wallet-aka-sign-in) +- [using-seed-phrase](#using-seed-phrase---Export-existing-account-using-a-seed-phrase) +- [using-private-key](#using-private-key---Export-existing-account-using-a-private-key) + + +#### using-web-wallet - Export existing account using NEAR Wallet + +To export an existing account, enter in the terminal command line: +```txt +near account \ + export-account volodymyr.testnet \ + using-web-wallet \ + network-config testnet +``` + +You will be redirected to the browser for authorization. +Default wallet url is https://app.mynearwallet.com/ (for testnet - https://testnet.mynearwallet.com/). But if you want to change to a different wallet url, you can use `--wallet-url` option: +```txt +near account \ + export-account volodymyr.testnet \ + using-web-wallet \ + network-config testnet\ + --wallet-url 'https://wallet.testnet.near.org/' +``` +
Demonstration of the command in interactive mode + + + +
+ +#### using-seed-phrase - Export existing account using a seed phrase + +To export an existing account, enter in the terminal command line: +```txt +near account \ + export-account volodymyr.testnet \ + using-seed-phrase \ + network-config testnet +``` + +
The result of this command will be as follows: + +```txt +Here is the secret recovery seed phrase for account : "feature army carpet ..." (HD Path: m/44'/397'/0'). +``` +
+ +#### using-private-key - Export existing account using a private key + +To export an existing account, enter in the terminal command line: +```txt +near account \ + export-account volodymyr.testnet \ + using-private-key \ + network-config testnet +``` + +
The result of this command will be as follows: + +```txt +Here is the private key for account : ed25519:4TKr1c7p...y7p8BvGdB +``` +
+ #### create-account - Create a new account - sponsor-by-linkdrop (Not implemented yet) @@ -871,6 +938,93 @@ https://explorer.testnet.near.org/transactions/6S7bJ76QNFypUvP7PCB1hkLM7X5GxPxP2 +#### manage-storage-deposit - Storage management: deposit, withdrawal, balance review + +- [view-balance](#view-balance---View-storage-balance-for-an-account) +- [deposit](#deposit---Make-a-storage-deposit-for-the-account) +- [withdraw](#withdraw---Withdraw-a-deposit-from-storage-for-an-account-ID) + +##### view-balance - View storage balance for an account + +To view the account balance on the contract on the last block, you must enter in the terminal command line: + +```txt +near account \ + manage-storage-deposit v1.social08.testnet \ + view-balance volodymyr.testnet \ + network-config testnet \ + now +``` + +
The result of this command will be as follows: + +```txt +storage balance for : + available: 1.6 MB (15.878059999854543210876557 NEAR [ 15878059999854543210876557 yoctoNEAR]) + total: 1.6 MB (16.238949999854543210876557 NEAR [ 16238949999854543210876557 yoctoNEAR]) +``` +
+ +
Demonstration of the command in interactive mode + + + +
+ +##### deposit - Make a storage deposit for the account + +To add a deposit to the account balance under the contract, you must enter in the terminal command line: + +```txt +near account \ + manage-storage-deposit v1.social08.testnet \ + deposit volodymyr.testnet '1 NEAR' \ + sign-as fro_volod.testnet \ + network-config testnet \ + sign-with-macos-keychain \ + send +``` + +
The result of this command will be as follows: + +```txt + has successfully added a deposit of 1 NEAR to on contract . +``` +
+ +
Demonstration of the command in interactive mode + + + +
+ +##### withdraw - Withdraw a deposit from storage for an account ID + +To withdraw funds from the account balance under the contract, you must enter in the terminal command line: + +```txt +near account \ + manage-storage-deposit v1.social08.testnet \ + withdraw '0.5 NEAR' \ + sign-as volodymyr.testnet \ + network-config testnet \ + sign-with-keychain \ + send +``` + +
The result of this command will be as follows: + +```txt + has successfully withdraw 0.5 NEAR from . +``` +
+ +
Demonstration of the command in interactive mode + + + +
+ ### tokens - Manage token assets such as NEAR, FT, NFT - [send-near](#send-near---The-transfer-is-carried-out-in-NEAR-tokens) - [send-ft](#send-ft---The-transfer-is-carried-out-in-FT-tokens) diff --git a/docs/GUIDE.ru.md b/docs/GUIDE.ru.md index 568f6b498..c4f2d59f0 100644 --- a/docs/GUIDE.ru.md +++ b/docs/GUIDE.ru.md @@ -89,12 +89,14 @@ near --offline tokens \ Просмотреть сведения об аккаунте ([View properties for an account](#view-account-summary---view-properties-for-an-account)) и просмотреть ключи доступа к аккаунту ([View a list of access keys of an account](#list-keys---View-a-list-of-access-keys-of-an-account)) возможно на текущий момент времени (***now***) и на определеный момент в прошлом, указав блок (***at-block-height*** или ***at-block-hash***). На примерах ниже показаны варианты применения этих режимов. - [view-account-summary](#view-account-summary---View-properties-for-an-account) -- [import-account](#import-account---import-existing-account-aka-sign-in) +- [import-account](#import-account---Import-existing-account-aka-sign-in) +- [export-account](#export-account---Export-existing-account) - [create-account](#create-account---Create-a-new-account) - [delete-account](#delete-account---Delete-an-account) - [list-keys](#list-keys---View-a-list-of-access-keys-of-an-account) - [add-key](#add-key---Add-an-access-key-to-an-account) - [delete-key](#delete-key---Delete-an-access-key-from-an-account) +- [manage-storage-deposit](#manage-storage-deposit---Storage-management-deposit-withdrawal-balance-review) #### view-account-summary - View properties for an account @@ -313,6 +315,72 @@ The data for the access key is saved in macOS Keychain +#### export-account - Export existing account + +- [using-web-wallet](#using-web-wallet---Export-existing-account-using-NEAR-Wallet-aka-sign-in) +- [using-seed-phrase](#using-seed-phrase---Export-existing-account-using-a-seed-phrase) +- [using-private-key](#using-private-key---Export-existing-account-using-a-private-key) + + +#### using-web-wallet - Export existing account using NEAR Wallet + +Для экспорта существующего аккаунта необходимо ввести в командной строке терминала: +```txt +near account \ + export-account volodymyr.testnet \ + using-web-wallet \ + network-config testnet +``` + +Вы будете перенаправлены браузер. +По умолчанию - это https://app.mynearwallet.com/ (для testnet - https://testnet.mynearwallet.com/). Но вы можете изменить адрес для авторизации с помощью флага `--wallet-url`: +```txt +near account \ + export-account volodymyr.testnet \ + using-web-wallet \ + network-config testnet\ + --wallet-url 'https://wallet.testnet.near.org/' +``` +
Демонстрация работы команды в интерактивном режиме + + + +
+ +#### using-seed-phrase - Export existing account using a seed phrase + +Для экспорта существующего аккаунта необходимо ввести в командной строке терминала: +```txt +near account \ + export-account volodymyr.testnet \ + using-seed-phrase \ + network-config testnet +``` + +
Результат выполнения команды + +```txt +Here is the secret recovery seed phrase for account : "feature army carpet ..." (HD Path: m/44'/397'/0'). +``` +
+ +#### using-private-key - Export existing account using a private key + +Для экспорта существующего аккаунта необходимо ввести в командной строке терминала: +```txt +near account \ + export-account volodymyr.testnet \ + using-private-key \ + network-config testnet +``` + +
Результат выполнения команды + +```txt +Here is the private key for account : ed25519:4TKr1c7p...y7p8BvGdB +``` +
+ #### create-account - Create a new account - sponsor-by-linkdrop (Находится в разработке) @@ -872,6 +940,93 @@ https://explorer.testnet.near.org/transactions/6S7bJ76QNFypUvP7PCB1hkLM7X5GxPxP2 +#### manage-storage-deposit - Storage management: deposit, withdrawal, balance review + +- [view-balance](#view-balance---View-storage-balance-for-an-account) +- [deposit](#deposit---Make-a-storage-deposit-for-the-account) +- [withdraw](#withdraw---Withdraw-a-deposit-from-storage-for-an-account-ID) + +##### view-balance - View storage balance for an account + +Для просмотра баланса аккаунта на контракте на последнем блоке необходимо ввести в командной строке терминала: + +```txt +near account \ + manage-storage-deposit v1.social08.testnet \ + view-balance volodymyr.testnet \ + network-config testnet \ + now +``` + +
Результат выполнения команды + +```txt +storage balance for : + available: 1.6 MB (15.878059999854543210876557 NEAR [ 15878059999854543210876557 yoctoNEAR]) + total: 1.6 MB (16.238949999854543210876557 NEAR [ 16238949999854543210876557 yoctoNEAR]) +``` +
+ +
Демонстрация работы команды в интерактивном режиме + + + +
+ +##### deposit - Make a storage deposit for the account + +Для пополнения баланса аккаунта на контракте необходимо ввести в командной строке терминала: + +```txt +near account \ + manage-storage-deposit v1.social08.testnet \ + deposit volodymyr.testnet '1 NEAR' \ + sign-as fro_volod.testnet \ + network-config testnet \ + sign-with-macos-keychain \ + send +``` + +
Результат выполнения команды + +```txt + has successfully added a deposit of 1 NEAR to on contract . +``` +
+ +
Демонстрация работы команды в интерактивном режиме + + + +
+ +##### withdraw - Withdraw a deposit from storage for an account ID + +Для вывода средств с баланса аккаунта на контракте необходимо ввести в командной строке терминала: + +```txt +near account \ + manage-storage-deposit v1.social08.testnet \ + withdraw '0.5 NEAR' \ + sign-as volodymyr.testnet \ + network-config testnet \ + sign-with-keychain \ + send +``` + +
Результат выполнения команды + +```txt + has successfully withdraw 0.5 NEAR from . +``` +
+ +
Демонстрация работы команды в интерактивном режиме + + + +
+ ### tokens - Manage token assets such as NEAR, FT, NFT - [send-near](#send-near---The-transfer-is-carried-out-in-NEAR-tokens) - [send-ft](#send-ft---The-transfer-is-carried-out-in-FT-tokens) diff --git a/src/commands/account/export_account/mod.rs b/src/commands/account/export_account/mod.rs new file mode 100644 index 000000000..c0c0d842f --- /dev/null +++ b/src/commands/account/export_account/mod.rs @@ -0,0 +1,221 @@ +use color_eyre::eyre::{ContextCompat, WrapErr}; +use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + +use crate::common::JsonRpcClientExt; +use crate::common::RpcQueryResponseExt; + +mod using_private_key; +mod using_seed_phrase; +mod using_web_wallet; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = crate::GlobalContext)] +#[interactive_clap(output_context = ExportAccountContext)] +pub struct ExportAccount { + #[interactive_clap(skip_default_input_arg)] + /// Which account ID should be exported? + account_id: crate::types::account_id::AccountId, + #[interactive_clap(subcommand)] + export_account_actions: ExportAccountActions, +} + +#[derive(Debug, Clone)] +pub struct ExportAccountContext { + global_context: crate::GlobalContext, + account_id: near_primitives::types::AccountId, +} + +impl ExportAccountContext { + pub fn from_previous_context( + previous_context: crate::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + Ok(Self { + global_context: previous_context, + account_id: scope.account_id.clone().into(), + }) + } +} + +impl ExportAccount { + pub fn input_account_id( + context: &crate::GlobalContext, + ) -> color_eyre::eyre::Result> { + crate::common::input_signer_account_id_from_used_account_list( + &context.config.credentials_home_dir, + "Which account ID should be exported?", + ) + } +} + +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(context = ExportAccountContext)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +/// How would you like to export the account? +pub enum ExportAccountActions { + #[strum_discriminants(strum( + message = "using-web-wallet - Export existing account using NEAR Wallet" + ))] + /// Export existing account using NEAR Wallet + UsingWebWallet(self::using_web_wallet::ExportAccountFromWebWallet), + #[strum_discriminants(strum( + message = "using-seed-phrase - Export existing account using a seed phrase" + ))] + /// Export existing account using a seed phrase + UsingSeedPhrase(self::using_seed_phrase::ExportAccountFromSeedPhrase), + #[strum_discriminants(strum( + message = "using-private-key - Export existing account using a private key" + ))] + /// Export existing account using a private key + UsingPrivateKey(self::using_private_key::ExportAccountFromPrivateKey), +} + +pub fn get_account_key_pair_from_keychain( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, +) -> color_eyre::eyre::Result { + let password = get_password_from_keychain(network_config, account_id)?; + let account_key_pair = serde_json::from_str(&password); + account_key_pair.wrap_err("Error reading data") +} + +pub fn get_password_from_keychain( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, +) -> color_eyre::eyre::Result { + let service_name: std::borrow::Cow<'_, str> = std::borrow::Cow::Owned(format!( + "near-{}-{}", + network_config.network_name, + account_id.as_str() + )); + let password = { + let access_key_list = network_config + .json_rpc_client() + .blocking_call_view_access_key_list( + account_id, + near_primitives::types::Finality::Final.into(), + ) + .wrap_err_with(|| format!("Failed to fetch access key list for {}", account_id))? + .access_key_list_view()?; + + access_key_list + .keys + .into_iter() + .filter(|key| { + matches!( + key.access_key.permission, + near_primitives::views::AccessKeyPermissionView::FullAccess + ) + }) + .map(|key| key.public_key) + .find_map(|public_key| { + let keyring = + keyring::Entry::new(&service_name, &format!("{}:{}", account_id, public_key)) + .ok()?; + keyring.get_password().ok() + }) + .wrap_err("No access keys found in keychain")? + }; + Ok(password) +} + +pub fn get_account_key_pair_from_legacy_keychain( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, + credentials_home_dir: &std::path::Path, +) -> color_eyre::eyre::Result { + let data_path = + get_account_key_pair_data_path(network_config, account_id, credentials_home_dir)?; + let data = std::fs::read_to_string(&data_path).wrap_err("Access key file not found!")?; + let account_key_pair: crate::transaction_signature_options::AccountKeyPair = + serde_json::from_str(&data) + .wrap_err_with(|| format!("Error reading data from file: {:?}", &data_path))?; + Ok(account_key_pair) +} + +fn get_account_key_pair_data_path( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, + credentials_home_dir: &std::path::Path, +) -> color_eyre::eyre::Result { + let check_if_seed_phrase_exists = false; + get_account_properties_data_path( + network_config, + account_id, + credentials_home_dir, + check_if_seed_phrase_exists, + ) +} + +pub fn get_account_properties_data_path( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, + credentials_home_dir: &std::path::Path, + check_if_seed_phrase_exists: bool, +) -> color_eyre::eyre::Result { + let file_name = format!("{}.json", account_id); + let mut path = std::path::PathBuf::from(credentials_home_dir); + + let dir_name = network_config.network_name.clone(); + path.push(&dir_name); + path.push(file_name); + + if path.exists() { + if !check_if_seed_phrase_exists { + return Ok(path); + } + let data = std::fs::read_to_string(&path).wrap_err("Access key file not found!")?; + if serde_json::from_str::(&data).is_ok() { + return Ok(path); + } + } + + let access_key_list = network_config + .json_rpc_client() + .blocking_call_view_access_key_list( + account_id, + near_primitives::types::Finality::Final.into(), + ) + .wrap_err_with(|| format!("Failed to fetch access KeyList for {}", account_id))? + .access_key_list_view()?; + let mut path = std::path::PathBuf::from(credentials_home_dir); + path.push(dir_name); + path.push(account_id.to_string()); + let mut data_path = std::path::PathBuf::new(); + for access_key in access_key_list.keys { + let account_public_key = access_key.public_key.to_string().replace(':', "_"); + match &access_key.access_key.permission { + near_primitives::views::AccessKeyPermissionView::FullAccess => {} + near_primitives::views::AccessKeyPermissionView::FunctionCall { .. } => { + continue; + } + } + let dir = path + .read_dir() + .wrap_err("There are no access keys found in the keychain for the account.")?; + for entry in dir.flatten() { + if entry + .path() + .file_stem() + .unwrap() + .to_str() + .unwrap() + .contains(&account_public_key) + { + data_path.push(entry.path()); + if !check_if_seed_phrase_exists { + return Ok(data_path); + } + let data = + std::fs::read_to_string(&data_path).wrap_err("Access key file not found!")?; + serde_json::from_str::(&data).wrap_err_with(|| format!( + "There are no master seed phrase in keychain to export for account <{account_id}>." + ))?; + return Ok(data_path); + } + } + } + Err(color_eyre::eyre::Report::msg(format!( + "There are no access keys in keychain to export for account <{account_id}>." + ))) +} diff --git a/src/commands/account/export_account/using_private_key/mod.rs b/src/commands/account/export_account/using_private_key/mod.rs new file mode 100644 index 000000000..7a0ad2c4e --- /dev/null +++ b/src/commands/account/export_account/using_private_key/mod.rs @@ -0,0 +1,65 @@ +use color_eyre::eyre::WrapErr; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = super::ExportAccountContext)] +#[interactive_clap(output_context = ExportAccountFromPrivateKeyContext)] +pub struct ExportAccountFromPrivateKey { + #[interactive_clap(named_arg)] + /// Select network + network_config: crate::network::Network, +} + +#[derive(Clone)] +pub struct ExportAccountFromPrivateKeyContext(crate::network::NetworkContext); + +impl ExportAccountFromPrivateKeyContext { + pub fn from_previous_context( + previous_context: super::ExportAccountContext, + _scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let config = previous_context.global_context.config.clone(); + let account_id = previous_context.account_id.clone(); + + let on_after_getting_network_callback: crate::network::OnAfterGettingNetworkCallback = + std::sync::Arc::new({ + move |network_config| { + if let Ok(account_key_pair) = + super::get_account_key_pair_from_keychain(network_config, &account_id) + { + println!( + "Here is the private key for account <{}>: {}", + account_id, account_key_pair.private_key, + ); + return Ok(()); + } + + let account_key_pair = super::get_account_key_pair_from_legacy_keychain( + network_config, + &account_id, + &config.credentials_home_dir, + ) + .wrap_err_with(|| { + format!("There are no access keys in keychain to export for account <{account_id}>.") + })?; + + println!( + "Here is the private key for account <{}>: {}", + account_id, account_key_pair.private_key, + ); + Ok(()) + } + }); + + Ok(Self(crate::network::NetworkContext { + config: previous_context.global_context.config, + interacting_with_account_ids: vec![previous_context.account_id], + on_after_getting_network_callback, + })) + } +} + +impl From for crate::network::NetworkContext { + fn from(item: ExportAccountFromPrivateKeyContext) -> Self { + item.0 + } +} diff --git a/src/commands/account/export_account/using_seed_phrase/mod.rs b/src/commands/account/export_account/using_seed_phrase/mod.rs new file mode 100644 index 000000000..c73a06938 --- /dev/null +++ b/src/commands/account/export_account/using_seed_phrase/mod.rs @@ -0,0 +1,86 @@ +use color_eyre::eyre::WrapErr; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = super::ExportAccountContext)] +#[interactive_clap(output_context = ExportAccountFromSeedPhraseContext)] +pub struct ExportAccountFromSeedPhrase { + #[interactive_clap(named_arg)] + /// Select network + network_config: crate::network::Network, +} + +#[derive(Clone)] +pub struct ExportAccountFromSeedPhraseContext(crate::network::NetworkContext); + +impl ExportAccountFromSeedPhraseContext { + pub fn from_previous_context( + previous_context: super::ExportAccountContext, + _scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let config = previous_context.global_context.config.clone(); + let account_id = previous_context.account_id.clone(); + + let on_after_getting_network_callback: crate::network::OnAfterGettingNetworkCallback = + std::sync::Arc::new({ + move |network_config| { + if let Ok(password) = + super::get_password_from_keychain(network_config, &account_id) + { + if let Ok(key_pair_properties) = + serde_json::from_str::(&password) + { + println!( + "Here is the secret recovery seed phrase for account <{}>: \"{}\" (HD Path: {}).", + account_id, key_pair_properties.master_seed_phrase, key_pair_properties.seed_phrase_hd_path + ); + return Ok(()); + } + } + + let data_path = get_seed_phrase_data_path( + network_config, + &account_id, + &config.credentials_home_dir, + )?; + + let data = std::fs::read_to_string(&data_path) + .wrap_err("Access key file not found!")?; + let key_pair_properties: crate::common::KeyPairProperties = + serde_json::from_str(&data).wrap_err_with(|| { + format!("Error reading data from file: {:?}", &data_path) + })?; + println!( + "Here is the secret recovery seed phrase for account <{}>: \"{}\" (HD Path: {}).", + account_id, key_pair_properties.master_seed_phrase, key_pair_properties.seed_phrase_hd_path + ); + Ok(()) + } + }); + + Ok(Self(crate::network::NetworkContext { + config: previous_context.global_context.config, + interacting_with_account_ids: vec![previous_context.account_id], + on_after_getting_network_callback, + })) + } +} + +impl From for crate::network::NetworkContext { + fn from(item: ExportAccountFromSeedPhraseContext) -> Self { + item.0 + } +} + +fn get_seed_phrase_data_path( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, + credentials_home_dir: &std::path::Path, +) -> color_eyre::eyre::Result { + let check_if_seed_phrase_exists = true; + super::get_account_properties_data_path( + network_config, + account_id, + credentials_home_dir, + check_if_seed_phrase_exists, + ) +} diff --git a/src/commands/account/export_account/using_web_wallet/mod.rs b/src/commands/account/export_account/using_web_wallet/mod.rs new file mode 100644 index 000000000..79a17808f --- /dev/null +++ b/src/commands/account/export_account/using_web_wallet/mod.rs @@ -0,0 +1,76 @@ +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = super::ExportAccountContext)] +#[interactive_clap(output_context = ExportAccountFromWebWalletContext)] +pub struct ExportAccountFromWebWallet { + #[interactive_clap(named_arg)] + /// Select network + network_config: crate::network::Network, +} + +#[derive(Clone)] +pub struct ExportAccountFromWebWalletContext(crate::network::NetworkContext); + +impl ExportAccountFromWebWalletContext { + pub fn from_previous_context( + previous_context: super::ExportAccountContext, + _scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let config = previous_context.global_context.config.clone(); + let account_id = previous_context.account_id.clone(); + + let on_after_getting_network_callback: crate::network::OnAfterGettingNetworkCallback = + std::sync::Arc::new({ + move |network_config| { + if let Ok(account_key_pair) = + super::get_account_key_pair_from_keychain(network_config, &account_id) + { + return auto_import_secret_key( + network_config, + &account_id, + &account_key_pair.private_key, + ); + } + + let account_key_pair = super::get_account_key_pair_from_legacy_keychain( + network_config, + &account_id, + &config.credentials_home_dir, + )?; + auto_import_secret_key( + network_config, + &account_id, + &account_key_pair.private_key, + ) + } + }); + + Ok(Self(crate::network::NetworkContext { + config: previous_context.global_context.config, + interacting_with_account_ids: vec![previous_context.account_id], + on_after_getting_network_callback, + })) + } +} + +impl From for crate::network::NetworkContext { + fn from(item: ExportAccountFromWebWalletContext) -> Self { + item.0 + } +} + +fn auto_import_secret_key( + network_config: &crate::config::NetworkConfig, + account_id: &near_primitives::types::AccountId, + private_key: &near_crypto::SecretKey, +) -> crate::CliResult { + let mut url: url::Url = network_config.wallet_url.join("auto-import-secret-key")?; + let fragment = format!("{}/{}", account_id, private_key); + url.set_fragment(Some(&fragment)); + eprintln!( + "If your browser doesn't automatically open, please visit this URL:\n {}\n", + &url.as_str() + ); + // url.open(); + open::that(url.as_ref()).ok(); + Ok(()) +} diff --git a/src/commands/account/mod.rs b/src/commands/account/mod.rs index d448ab94c..d8a6675bd 100644 --- a/src/commands/account/mod.rs +++ b/src/commands/account/mod.rs @@ -4,6 +4,7 @@ mod add_key; pub mod create_account; mod delete_account; mod delete_key; +mod export_account; mod import_account; mod list_keys; pub mod storage_management; @@ -34,6 +35,9 @@ pub enum AccountActions { ))] /// Import existing account (a.k.a. "sign in") ImportAccount(self::import_account::ImportAccountCommand), + #[strum_discriminants(strum(message = "export-account - Export existing account"))] + /// Export existing account + ExportAccount(self::export_account::ExportAccount), #[strum_discriminants(strum(message = "create-account - Create a new account"))] /// Create a new account CreateAccount(self::create_account::CreateAccount), diff --git a/src/transaction_signature_options/mod.rs b/src/transaction_signature_options/mod.rs index df2bf01e9..a0de1ec87 100644 --- a/src/transaction_signature_options/mod.rs +++ b/src/transaction_signature_options/mod.rs @@ -20,12 +20,12 @@ pub const META_TRANSACTION_VALID_FOR_DEFAULT: u64 = 1000; /// Select a tool for signing the transaction: pub enum SignWith { #[strum_discriminants(strum( - message = "sign-with-keychain - Sign the transaction with a key saved in the keychain (backwards compatible with the old near CLI)" + message = "sign-with-keychain - Sign the transaction with a key saved in the secure keychain" ))] /// Sign the transaction with a key saved in keychain SignWithKeychain(self::sign_with_keychain::SignKeychain), #[strum_discriminants(strum( - message = "sign-with-legacy-keychain - Sign the transaction with a key saved in legacy keychain (compatible with the old near CLI)" + message = "sign-with-legacy-keychain - Sign the transaction with a key saved in legacy keychain (compatible with the old near CLI)" ))] /// Sign the transaction with a key saved in legacy keychain (compatible with the old near CLI) SignWithLegacyKeychain(self::sign_with_legacy_keychain::SignLegacyKeychain),