Newer
Older
Nicolas80
committed
use crate::commands::cesium::compute_g1v1_public_key;
use crate::entities::vault_account;
use crate::entities::vault_account::{AccountTreeNode, ActiveModel, DbAccountId};
use crate::*;
use age::secrecy::Secret;
Nicolas80
committed
use comfy_table::{Cell, Table};
use sea_orm::ActiveValue::Set;
Nicolas80
committed
use sea_orm::{ConnectionTrait, TransactionTrait};
use sp_core::crypto::AddressUri;
use std::cell::RefCell;
Nicolas80
committed
use std::path::PathBuf;
use std::rc::Rc;
Nicolas80
committed
#[derive(Clone, Debug, clap::Parser)]
Nicolas80
committed
/// List available SS58 Addresses in the vault
Nicolas80
committed
#[clap(subcommand)]
List(ListChoice),
Nicolas80
committed
/// Use specific SS58 Address (changes the config Address)
Nicolas80
committed
Use {
#[clap(flatten)]
address_or_vault_name: AddressOrVaultNameGroup,
},
/// Generate a mnemonic
Generate,
/// Import key from (substrate uri) or other format with interactive prompt
#[clap(
long_about = "Import key from (substrate uri) or other format with interactive prompt.\n\
\n\
This will create a <Base> account in the vault for the provided/computed Substrate URI \n\
and associated SS58 Address.\n\
\n\
If using default format (or specifically \"substrate\") a derivation path is supported\n\
)]
Nicolas80
committed
Import {
/// Secret key format (substrate, seed, cesium)
#[clap(short = 'S', long, required = false, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
},
/// Add a derivation to an existing account
#[clap(long_about = "Add a derivation to an existing account.\n\
\n\
Only \"substrate\" and \"seed\" format are supported for derivations.\n\
\n\
Use command `vault list base` to see available <Base> account and their format\n\
And then use command 'vault list for' to find all accounts linked to that <Base> account.")]
Nicolas80
committed
#[clap(alias = "deriv")]
#[clap(alias = "derivation")]
Derive {
Nicolas80
committed
#[clap(flatten)]
address_or_vault_name: AddressOrVaultNameGroup,
},
Nicolas80
committed
/// Give a meaningful name to an SS58 Address in the vault
Nicolas80
committed
Rename {
/// SS58 Address
Nicolas80
committed
address: AccountId,
},
/// Remove an SS58 Address from the vault together with its linked derivations
Nicolas80
committed
#[clap(long_about = "Remove an SS58 Address from the vault\n\
\n\
If a <Base> Address is given it will also remove the saved key")]
Nicolas80
committed
Remove {
#[clap(flatten)]
address_or_vault_name: AddressOrVaultNameGroup,
},
/// Inspect a vault entry, retrieving its Substrate URI (will provide more data in a future version)
Inspect {
#[clap(flatten)]
address_or_vault_name: AddressOrVaultNameGroup,
},
/// (deprecated) List available key files (needs to be migrated with command `vault migrate` in order to use them)
#[deprecated(
note = "Should be removed in a future version when db persistence of vault is present for a while"
)]
Nicolas80
committed
ListFiles,
/// (deprecated) Migrate old key files into db (will have to provide password for each key)
#[deprecated(
note = "Should be removed in a future version when db persistence of vault is present for a while"
)]
Nicolas80
committed
Migrate,
/// Show where vault db (or old keys) is stored
Where,
}
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum ListChoice {
/// List all <Base> accounts and their linked derivations SS58 Addresses in the vault
Nicolas80
committed
#[default]
All,
/// List <Base> and Derivation SS58 Addresses linked to the selected one
For {
#[clap(flatten)]
address_or_vault_name: AddressOrVaultNameGroup,
},
/// List all <Base> SS58 Addresses in the vault
Base,
Nicolas80
committed
}
#[derive(Debug, clap::Args, Clone)]
#[group(required = true, multiple = false)]
pub struct AddressOrVaultNameGroup {
/// SS58 Address
#[clap(short)]
address: Option<AccountId>,
/// Name of an SS58 Address in the vault
#[clap(short = 'v')]
name: Option<String>,
}
Nicolas80
committed
pub struct VaultDataToImport {
secret_format: SecretFormat,
Nicolas80
committed
secret_suri: String,
Nicolas80
committed
key_pair: KeyPair,
}
// encrypt input with passphrase
pub fn encrypt(input: &[u8], passphrase: String) -> Result<Vec<u8>, age::EncryptError> {
let encryptor = age::Encryptor::with_user_passphrase(Secret::new(passphrase));
let mut encrypted = vec![];
let mut writer = encryptor.wrap_output(age::armor::ArmoredWriter::wrap_output(
&mut encrypted,
age::armor::Format::AsciiArmor,
)?)?;
writer.write_all(input)?;
writer.finish().and_then(|armor| armor.finish())?;
Ok(encrypted)
}
// decrypt cypher with passphrase
pub fn decrypt(input: &[u8], passphrase: String) -> Result<Vec<u8>, age::DecryptError> {
let age::Decryptor::Passphrase(decryptor) =
age::Decryptor::new(age::armor::ArmoredReader::new(input))?
else {
unimplemented!()
};
let mut decrypted = vec![];
let mut reader = decryptor.decrypt(&Secret::new(passphrase.to_owned()), None)?;
reader.read_to_end(&mut decrypted)?;
Ok(decrypted)
}
Nicolas80
committed
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
Nicolas80
committed
let db = data.connect_db();
// match subcommand
match command {
Nicolas80
committed
Subcommand::List(choice) => match choice {
ListChoice::All => {
let all_account_tree_node_hierarchies =
vault_account::fetch_all_base_account_tree_node_hierarchies(db).await?;
let table = compute_vault_accounts_table(&all_account_tree_node_hierarchies)?;
Nicolas80
committed
Nicolas80
committed
println!("available SS58 Addresses:");
Nicolas80
committed
println!("{table}");
}
ListChoice::Base => {
let base_account_tree_nodes =
vault_account::fetch_only_base_account_tree_nodes(db).await?;
Nicolas80
committed
let table = compute_vault_accounts_table(&base_account_tree_nodes)?;
Nicolas80
committed
println!("available <Base> SS58 Addresses:");
println!("{table}");
}
ListChoice::For {
address_or_vault_name,
} => {
let account_tree_node =
retrieve_account_tree_node(db, address_or_vault_name).await?;
let base_account_tree_node =
vault_account::get_base_account_tree_node(&account_tree_node);
let table = compute_vault_accounts_table(&[base_account_tree_node])?;
println!(
"available SS58 Addresses linked to {}:",
account_tree_node.borrow().account
);
Nicolas80
committed
println!("{table}");
Nicolas80
committed
},
Subcommand::ListFiles => {
let vault_key_addresses = fetch_vault_key_addresses(&data).await?;
let table = compute_vault_key_files_table(&vault_key_addresses).await?;
println!("available key files (needs to be migrated with command `vault migrate` in order to use them):");
Nicolas80
committed
println!("{table}");
Nicolas80
committed
Subcommand::Use {
address_or_vault_name,
} => {
let account = retrieve_vault_account(db, address_or_vault_name).await?;
Nicolas80
committed
println!("Using: {}", account);
let updated_cfg = conf::Config {
address: Some(account.address.0),
..data.cfg
};
//This updated configuration will be picked up with next GCli execution
conf::save(&updated_cfg);
}
Subcommand::Generate => {
// TODO allow custom word count
let mnemonic = bip39::Mnemonic::generate(12).unwrap();
println!("{mnemonic}");
}
Nicolas80
committed
Subcommand::Import { secret_format } => {
let vault_data_for_import =
prompt_secret_and_compute_vault_data_to_import(secret_format)?;
Nicolas80
committed
//Extra check for SecretFormat::Cesium / G1v1Seed - showing the G1v1 cesium public key for confirmation
if secret_format == SecretFormat::Cesium {
println!(
"The G1v1 public key for the provided secret is: '{}'",
compute_g1v1_public_key(&vault_data_for_import.key_pair)?
);
let confirmed = inputs::confirm_action("Is it the correct one (if not, you should try again to input Cesium id/password) ?".to_string())?;
if !confirmed {
return Ok(());
}
}
Nicolas80
committed
let txn = db.begin().await?;
Nicolas80
committed
println!();
let _account =
create_base_account_for_vault_data_to_import(&txn, &vault_data_for_import, None)
.await?;
Nicolas80
committed
txn.commit().await?;
println!("Change done");
Nicolas80
committed
}
Subcommand::Derive {
Nicolas80
committed
address_or_vault_name,
} => {
let account_tree_node_to_derive =
retrieve_account_tree_node(db, address_or_vault_name).await?;
Nicolas80
committed
let account_to_derive = account_tree_node_to_derive.borrow().account.clone();
Nicolas80
committed
let base_account_tree_node =
vault_account::get_base_account_tree_node(&account_tree_node_to_derive);
Nicolas80
committed
let base_account = &base_account_tree_node.borrow().account.clone();
if base_account.crypto_scheme.is_none() {
return Err(GcliError::DatabaseError(DbErr::Custom(format!("Crypto scheme is not set for the base account:{base_account} - should never happen"))));
}
if let Some(crypto_scheme) = base_account.crypto_scheme {
if CryptoScheme::from(crypto_scheme) == CryptoScheme::Ed25519 {
println!(
"Only \"{}\" and \"{}\" format are supported for derivations",
Into::<&str>::into(SecretFormat::Substrate),
Into::<&str>::into(SecretFormat::Seed)
);
println!();
println!(
"Use command `vault list base` to see available <Base> account and their format\n\
And then use command 'vault list for' to find all accounts linked to that <Base> account"
);
return Ok(());
}
}
println!("Adding derivation to: {account_to_derive}");
Nicolas80
committed
let base_parent_hierarchy_account_tree_node_to_derive =
vault_account::get_base_parent_hierarchy_account_tree_node(
&account_tree_node_to_derive,
);
let parent_hierarchy_table_account_to_derive =
compute_vault_accounts_table(&[base_parent_hierarchy_account_tree_node_to_derive])?;
println!();
println!("Its parent hierarchy is this:");
println!("{parent_hierarchy_table_account_to_derive}");
println!();
println!("The linked <Base> account is {base_account}");
println!("Enter password to decrypt the <Base> account key");
let password = inputs::prompt_password()?;
Nicolas80
committed
let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node(
&account_tree_node_to_derive,
password,
)?;
Nicolas80
committed
Nicolas80
committed
let derivation_path = inputs::prompt_vault_derivation_path()?;
let derivation_secret_suri =
format!("{account_to_derive_secret_suri}{derivation_path}");
Nicolas80
committed
let derivation_keypair =
compute_keypair(CryptoScheme::Sr25519, &derivation_secret_suri)?;
Nicolas80
committed
let derivation_address: String = derivation_keypair.address().to_string();
let txn = db.begin().await?;
Nicolas80
committed
println!();
let _derivation = create_derivation_account(
&txn,
&derivation_address,
&derivation_path,
&account_to_derive.address.to_string(),
)
.await?;
txn.commit().await?;
println!("Change done");
Nicolas80
committed
}
Subcommand::Rename { address } => {
let account =
vault_account::find_by_id(db, &DbAccountId::from(address.clone())).await?;
Nicolas80
committed
if account.is_none() {
Nicolas80
committed
println!("No vault entry found for address:'{address}'");
println!("You might want to import it first with 'vault import'");
return Ok(());
}
let account = account.unwrap();
Nicolas80
committed
println!(
"Current name for address:'{address}' is {:?}",
&account.name
Nicolas80
committed
);
println!("Enter new name for address (leave empty to remove the name)");
let name =
inputs::prompt_vault_name_and_check_availability(db, account.name.as_ref()).await?;
Nicolas80
committed
let _account = vault_account::update_account_name(db, account, name.as_ref()).await?;
println!("Rename done");
Nicolas80
committed
}
Subcommand::Remove {
address_or_vault_name,
} => {
let account_tree_node_to_delete =
retrieve_account_tree_node(db, address_or_vault_name).await?;
Nicolas80
committed
Nicolas80
committed
let txn = db.begin().await?;
Nicolas80
committed
let account_to_delete = account_tree_node_to_delete.borrow().account.clone();
let address_to_delete = account_tree_node_to_delete.borrow().account.address.clone();
Nicolas80
committed
//If account to delete has children; also delete all linked derivations
if !account_tree_node_to_delete.borrow().children.is_empty() {
let table = compute_vault_accounts_table(&[account_tree_node_to_delete.clone()])?;
Nicolas80
committed
println!("All addresses linked to: {account_to_delete}");
Nicolas80
committed
println!("{table}");
println!(
"This {} account has {} addresses in total",
account_to_delete.account_type(),
vault_account::count_accounts_in_account_tree_node_and_children(
&account_tree_node_to_delete
)
Nicolas80
committed
);
let confirmation_message = if account_to_delete.is_base_account() {
"Are you sure you want to delete it along with the saved key?"
};
let confirmed = inputs::confirm_action(confirmation_message.to_string())?;
Nicolas80
committed
if !confirmed {
return Ok(());
}
for account_to_delete in
vault_account::extract_accounts_depth_first_from_account_tree_node(
&account_tree_node_to_delete,
)? {
let delete_result = account_to_delete.delete(&txn).await?;
println!("Deleting {} address", delete_result.rows_affected);
Nicolas80
committed
}
} else {
let delete_result = account_to_delete.delete(&txn).await?;
println!("Deleting {} address", delete_result.rows_affected);
Nicolas80
committed
}
txn.commit().await?;
println!("Done removing address:'{address_to_delete}'");
}
Subcommand::Inspect {
address_or_vault_name,
} => {
let account_tree_node_to_derive =
retrieve_account_tree_node(db, address_or_vault_name).await?;
println!("Enter password to decrypt the <Base> account key");
let password = inputs::prompt_password()?;
let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node(
&account_tree_node_to_derive,
password,
)?;
println!("Substrate URI: '{account_to_derive_secret_suri}'")
}
Nicolas80
committed
Subcommand::Migrate => {
println!("Migrating existing key files to db");
let vault_key_addresses = fetch_vault_key_addresses(&data).await?;
let table = compute_vault_key_files_table(&vault_key_addresses).await?;
println!("available key files to possibly migrate:");
println!("{table}");
for address in vault_key_addresses {
//Check if we already have a vault_derivation for that address
let existing_account =
vault_account::find_by_id(db, &DbAccountId::from_str(&address)?).await?;
Nicolas80
committed
if existing_account.is_some() {
Nicolas80
committed
//Already migrated
continue;
}
println!();
println!("Trying to migrate key {address}");
let vault_data_from_file = match try_fetch_vault_data_from_file(&data, &address) {
Ok(Some(vault_data)) => vault_data,
Ok(None) => {
println!("No vault entry file found for address {address}");
continue;
}
Err(e) => {
println!("Error while fetching vault data for address {address}: {e}");
println!("Continuing to next one");
continue;
}
};
let vault_data_to_import = VaultDataToImport {
Nicolas80
committed
secret_format: vault_data_from_file.secret_format,
secret_suri: vault_data_from_file.secret,
Nicolas80
committed
key_pair: vault_data_from_file.key_pair,
};
Nicolas80
committed
let txn = db.begin().await?;
Nicolas80
committed
let account = create_base_account_for_vault_data_to_import(
Nicolas80
committed
&txn,
&vault_data_to_import,
Some(&vault_data_from_file.password),
Nicolas80
committed
)
Nicolas80
committed
match account {
Ok(_account) => {
txn.commit().await?;
println!("Change done");
}
Err(error) => {
println!("Error occurred: {error}");
println!("Continuing to next key");
}
}
Nicolas80
committed
}
println!("Migration done");
}
Subcommand::Where => {
println!("{}", data.project_dir.data_dir().to_str().unwrap());
}
};
Ok(())
}
/// Method used to separate vault `name` part from optional `derivation` part in computed names that can be provided by users in the different `vault` commands using `AddressOrVaultNameGroup`
fn parse_vault_name_and_derivation_path_from_user_input(
user_input_name: String,
Nicolas80
committed
) -> Result<(String, Option<String>), GcliError> {
if user_input_name.contains("/") {
user_input_name.find("/").map_or(
Err(GcliError::Input("Invalid format".to_string())),
|idx| {
let (prefix, derivation_path) = user_input_name.split_at(idx);
Nicolas80
committed
Ok((prefix.to_string(), Some(derivation_path.to_string())))
Nicolas80
committed
} else {
Nicolas80
committed
}
}
/// Method that can be used to parse a Substrate URI (which can also be only a derivation path)
///
/// Does some internal verification (done by sp_core::address_uri::AddressUri)
///
/// It extracts the (optional) `phrase` and the (optional) recomposed full `derivation path`
///
/// It also checks if a derivation `password` was provided and returns an error if one was found
pub fn parse_prefix_and_derivation_path_from_suri(
raw_string: String,
) -> Result<(Option<String>, Option<String>), GcliError> {
let address_uri =
AddressUri::parse(&raw_string).map_err(|e| GcliError::Input(e.to_string()))?;
if let Some(pass) = address_uri.pass {
return Err(GcliError::Input(format!(
"Having a password in the derivation path is not supported (password:'{}')",
pass
)));
}
let full_path = if address_uri.paths.is_empty() {
None
} else {
Some("/".to_owned() + &address_uri.paths.into_iter().collect::<Vec<_>>().join("/"))
};
Ok((address_uri.phrase.map(|s| s.to_string()), full_path))
}
fn map_secret_format_to_crypto_scheme(secret_format: SecretFormat) -> CryptoScheme {
Nicolas80
committed
match secret_format {
SecretFormat::Seed => CryptoScheme::Sr25519,
SecretFormat::Substrate => CryptoScheme::Sr25519,
SecretFormat::Predefined => CryptoScheme::Sr25519,
SecretFormat::Cesium => CryptoScheme::Ed25519,
Nicolas80
committed
}
}
/// This method will scan files in the data directory and return the addresses of the vault keys found
#[deprecated(
note = "Should be removed in a future version when db persistence of vault is present for a while"
)]
Nicolas80
committed
async fn fetch_vault_key_addresses(data: &Data) -> Result<Vec<String>, GcliError> {
let mut entries = std::fs::read_dir(data.project_dir.data_dir())?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, std::io::Error>>()?;
// To have consistent ordering
entries.sort();
let mut vault_key_addresses: Vec<String> = vec![];
entries.iter().for_each(|dir_path| {
let filename = dir_path.file_name().unwrap().to_str().unwrap();
// If potential_address is a valid AccountId
if AccountId::from_str(filename).is_ok() {
vault_key_addresses.push(filename.to_string());
Nicolas80
committed
}
});
Ok(vault_key_addresses)
}
#[deprecated(
note = "Should be removed in a future version when db persistence of vault is present for a while"
)]
Nicolas80
committed
async fn compute_vault_key_files_table(vault_key_addresses: &[String]) -> Result<Table, GcliError> {
let mut table = Table::new();
table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
table.set_header(vec!["Key file"]);
vault_key_addresses.iter().for_each(|address| {
table.add_row(vec![Cell::new(address)]);
});
Ok(table)
}
pub fn compute_vault_accounts_table(
account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
) -> Result<Table, GcliError> {
Nicolas80
committed
let mut table = Table::new();
table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
Nicolas80
committed
table.set_header(vec!["SS58 Address", "Format", "Account/Path", "Name"]);
for account_tree_node in account_tree_nodes {
add_account_tree_node_to_table(&mut table, account_tree_node);
}
Ok(table)
}
fn add_account_tree_node_to_table(
table: &mut Table,
account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) {
let row = compute_vault_accounts_row(account_tree_node);
table.add_row(row);
for child in &account_tree_node.borrow().children {
add_account_tree_node_to_table(table, child);
}
}
pub fn compute_vault_accounts_row(account_tree_node: &Rc<RefCell<AccountTreeNode>>) -> Vec<Cell> {
let empty_string = "".to_string();
let depth_account_tree_node = vault_account::count_depth_account_tree_node(account_tree_node);
let name = if let Some(name) = account_tree_node.borrow().account.name.clone() {
name
} else if let Some(computed_name) =
vault_account::compute_name_account_tree_node(account_tree_node)
{
format!("<{}>", computed_name)
} else {
empty_string.clone()
};
Nicolas80
committed
let account_tree_node = account_tree_node.borrow();
Nicolas80
committed
let address = if depth_account_tree_node > 0 {
let ancestors = "│ ".repeat(depth_account_tree_node - 1);
format!("{}├─{}", ancestors, account_tree_node.account.address)
} else {
account_tree_node.account.address.to_string()
};
Nicolas80
committed
let (path, format) = if let Some(path) = account_tree_node.account.path.clone() {
(path, empty_string.clone())
} else {
let secret_format = match account_tree_node.account.crypto_scheme.unwrap().into() {
CryptoScheme::Sr25519 => SecretFormat::Substrate,
CryptoScheme::Ed25519 => SecretFormat::Cesium,
Nicolas80
committed
};
let secret_format_str: &str = secret_format.into();
(
format!("<{}>", account_tree_node.account.account_type()),
secret_format_str.to_string(),
)
};
Nicolas80
committed
vec![
Cell::new(&address),
Cell::new(format),
Cell::new(&path),
Cell::new(&name),
]
Nicolas80
committed
}
pub async fn retrieve_vault_account_for_name<C>(
name_input: &String,
) -> Result<vault_account::Model, GcliError>
let account_tree_node = retrieve_account_tree_node_for_name(db, name_input).await?;
Nicolas80
committed
//Need this extra step to avoid borrowing issues
let account = account_tree_node.borrow().account.clone();
Ok(account)
Nicolas80
committed
}
pub async fn retrieve_account_tree_node<C>(
address_or_vault_name: AddressOrVaultNameGroup,
) -> Result<Rc<RefCell<AccountTreeNode>>, GcliError>
where
C: ConnectionTrait,
{
let account_tree_node = if let Some(name_input) = &address_or_vault_name.name {
retrieve_account_tree_node_for_name(db, name_input).await?
} else if let Some(address) = &address_or_vault_name.address {
let base_account_tree_node =
vault_account::fetch_base_account_tree_node_hierarchy_unwrapped(
db,
&address.to_string(),
)
.await?;
let account_tree_node_for_address = vault_account::get_account_tree_node_for_address(
&base_account_tree_node,
&address.to_string(),
);
Nicolas80
committed
Nicolas80
committed
} else {
//Should never happen since clap enforces exactly one of the 2 options
return Err(GcliError::Input("No address or name provided".to_string()));
};
pub async fn retrieve_account_tree_node_for_name<C>(
db: &C,
name_input: &String,
) -> Result<Rc<RefCell<AccountTreeNode>>, GcliError>
where
C: ConnectionTrait,
{
let (name, derivation_path_opt) =
parse_vault_name_and_derivation_path_from_user_input(name_input.to_string())?;
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
let account_for_name = vault_account::find_by_name(db, &name).await?;
let account_for_name = account_for_name.ok_or(GcliError::Input(format!(
"No account found with name:'{name}'"
)))?;
let base_account_tree_node = vault_account::fetch_base_account_tree_node_hierarchy_unwrapped(
db,
&account_for_name.address.to_string(),
)
.await?;
let account_tree_node_for_name = vault_account::get_account_tree_node_for_address(
&base_account_tree_node,
&account_for_name.address.to_string(),
);
Ok(match derivation_path_opt {
None => Rc::clone(&account_tree_node_for_name),
Some(path) => {
let account_tree_node_for_name_input =
vault_account::compute_name_map_for_account_tree_node(&account_tree_node_for_name)?
.get(name_input)
.cloned()
.ok_or(GcliError::Input(format!(
"No account found with name:'{name}' and path:'{path}'"
)))?;
Rc::clone(&account_tree_node_for_name_input)
}
})
}
pub async fn retrieve_vault_account<C>(
address_or_vault_name: AddressOrVaultNameGroup,
) -> Result<vault_account::Model, GcliError>
where
C: ConnectionTrait,
{
let account_tree_node = retrieve_account_tree_node(db, address_or_vault_name).await?;
//Need this extra step to avoid borrowing issues
let account = account_tree_node.borrow().account.clone();
Nicolas80
committed
}
fn create_vault_data_to_import<F, P>(
secret_format: SecretFormat,
prompt_fn: F,
) -> Result<VaultDataToImport, GcliError>
where
Nicolas80
committed
F: Fn() -> (String, P),
Nicolas80
committed
P: Into<KeyPair>,
{
let (secret, pair) = prompt_fn();
let key_pair = pair.into();
Ok(VaultDataToImport {
secret_format,
Nicolas80
committed
secret_suri: secret,
Nicolas80
committed
key_pair,
})
}
fn prompt_secret_and_compute_vault_data_to_import(
secret_format: SecretFormat,
) -> Result<VaultDataToImport, GcliError> {
match secret_format {
SecretFormat::Substrate => {
create_vault_data_to_import(secret_format, prompt_secret_substrate_and_compute_keypair)
}
SecretFormat::Seed => {
create_vault_data_to_import(secret_format, prompt_seed_and_compute_keypair)
}
SecretFormat::Cesium => {
create_vault_data_to_import(secret_format, prompt_secret_cesium_and_compute_keypair)
}
SecretFormat::Predefined => {
create_vault_data_to_import(secret_format, prompt_predefined_and_compute_keypair)
}
}
}
/// Creates a `base` vault account for vault_data provided and returns it
///
/// Does extra checks and asks for user input in case the address is already present in the vault.
Nicolas80
committed
///
/// Can request password and (optional) name to the user at the proper time
///
/// Typically used for `vault import|migrate` commands
pub async fn create_base_account_for_vault_data_to_import<C>(
db_tx: &C,
Nicolas80
committed
vault_data: &VaultDataToImport,
password: Option<&String>,
) -> Result<vault_account::Model, GcliError>
Nicolas80
committed
where
C: ConnectionTrait,
{
Nicolas80
committed
let address_to_import = vault_data.key_pair.address().to_string();
println!("Trying to import for SS58 address :'{}'", address_to_import);
println!();
let vault_account = if let Some(existing_vault_account) =
vault_account::find_by_id(db_tx, &DbAccountId::from(address_to_import.clone())).await?
Nicolas80
committed
{
if existing_vault_account.is_base_account() {
println!("You are trying to add {address_to_import} as a <Base> account while it already exists as a <Base> account.");
println!();
println!("Do you want to:");
println!("1. keep the existing <Base> account and cancel import");
println!("2. overwrite existing <Base> account with the new encrypted key (children will be re-parented)");
} else {
// Existing derivation account
let account_tree_node_hierarchy =
vault_account::fetch_base_account_tree_node_hierarchy_unwrapped(
db_tx,
&address_to_import,
)
.await?;
let account_tree_node_for_address = vault_account::get_account_tree_node_for_address(
&account_tree_node_hierarchy,
&address_to_import,
);
let base_parent_hierarchy_account_tree_node =
vault_account::get_base_parent_hierarchy_account_tree_node(
&account_tree_node_for_address,
);
let parent_hierarchy_table =
compute_vault_accounts_table(&[base_parent_hierarchy_account_tree_node])?;
println!("You are trying to add {address_to_import} as a <Base> account");
println!(
"but it is already present as `{}` derivation of {} account.",
existing_vault_account.path.clone().unwrap(),
existing_vault_account.parent.clone().unwrap()
);
println!();
println!("{parent_hierarchy_table}");
println!();
println!("Do you want to:");
println!("1. keep the existing derivation and cancel import");
println!("2. delete the derivation account and replace it with the new <Base> account (children will be re-parented)");
}
Nicolas80
committed
let result = inputs::select_action("Your choice?", vec!["1", "2"])?;
match result {
"2" => {
let encrypted_suri =
compute_encrypted_suri(password, vault_data.secret_suri.clone())?;
Nicolas80
committed
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
println!(
"(Optional) Enter a name for the vault entry (leave empty to remove the name)"
);
let name = inputs::prompt_vault_name_and_check_availability(
db_tx,
existing_vault_account.name.as_ref(),
)
.await?;
// Since links are made based on address / parent(address) we can just edit the existing entry and it should be fine
let mut vault_account: ActiveModel = existing_vault_account.into();
vault_account.path = Set(None);
vault_account.parent = Set(None);
vault_account.crypto_scheme = Set(Some(
map_secret_format_to_crypto_scheme(vault_data.secret_format).into(),
));
vault_account.encrypted_suri = Set(Some(encrypted_suri));
vault_account.name = Set(name.clone());
let updated_vault_account =
vault_account::update_account(db_tx, vault_account).await?;
println!("Updating vault account {updated_vault_account}");
updated_vault_account
}
_ => {
return Err(GcliError::Input("import canceled".into()));
}
}
} else {
//New entry
let secret_format = vault_data.secret_format;
let encrypted_suri = compute_encrypted_suri(password, vault_data.secret_suri.clone())?;
Nicolas80
committed
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
println!("(Optional) Enter a name for the vault entry");
let name = inputs::prompt_vault_name_and_check_availability(db_tx, None).await?;
let crypto_scheme = map_secret_format_to_crypto_scheme(secret_format);
let base_account = vault_account::create_base_account(
db_tx,
&address_to_import,
name.as_ref(),
crypto_scheme,
encrypted_suri,
)
.await?;
println!("Creating <Base> account {base_account}");
base_account
};
Ok(vault_account)
}
/// Creates a `derivation` vault account for data provided and returns it
///
/// Does extra checks and asks for user input in case the address is already present in the vault.
///
/// Can request (optional) name to the user at the proper time
///
/// Typically used for `vault derive` command
pub async fn create_derivation_account<C>(
db_tx: &C,
derivation_address: &String,
derivation_path: &String,
parent_address: &String,
) -> Result<vault_account::Model, GcliError>
where
C: ConnectionTrait,
{
println!("Trying to create derivation with address '{derivation_address}'");
println!();
let vault_account = if let Some(existing_vault_account) =
vault_account::find_by_id(db_tx, &DbAccountId::from(derivation_address.clone())).await?
{
// Existing account
println!("You are trying to derive '{derivation_path}' from parent '{parent_address}'");
if existing_vault_account.is_base_account() {
println!(
"but it is already present as a direct <Base> account '{}'",
existing_vault_account.address
);
println!("Do you want to:");
println!("1. keep the existing <Base> account and cancel import");
println!("2. delete the existing <Base> account and associated key and replace it with the new derivation account (children will be re-parented)");
} else {
//Existing derivation
let existing_account_tree_node_hierarchy =
vault_account::fetch_base_account_tree_node_hierarchy_unwrapped(
db_tx,
derivation_address,
)
.await?;
let existing_account_tree_node_for_address =
vault_account::get_account_tree_node_for_address(
&existing_account_tree_node_hierarchy,
derivation_address,
);
let base_parent_hierarchy_existing_account_tree_node =
vault_account::get_base_parent_hierarchy_account_tree_node(
&existing_account_tree_node_for_address,
);
let parent_hierarchy_table_existing_account =
compute_vault_accounts_table(&[base_parent_hierarchy_existing_account_tree_node])?;
println!(
"but it is already present as `{}` derivation of '{}' account.",
existing_vault_account.path.clone().unwrap(),
existing_vault_account.parent.clone().unwrap()
);
println!();
println!("{parent_hierarchy_table_existing_account}");
println!();
println!("Do you want to:");
println!("1. keep the existing derivation and cancel import");
println!("2. delete the derivation account and replace it with this new derivation (children will be re-parented)");
}
let result = inputs::select_action("Your choice?", vec!["1", "2"])?;
match result {
"2" => {
println!(
"(Optional) Enter a name for the vault entry (leave empty to remove the name)"
);
let name = inputs::prompt_vault_name_and_check_availability(
db_tx,
existing_vault_account.name.as_ref(),
)