diff --git a/bin/drk/src/interactive.rs b/bin/drk/src/interactive.rs index 3d312e83a..48c780e1d 100644 --- a/bin/drk/src/interactive.rs +++ b/bin/drk/src/interactive.rs @@ -45,7 +45,7 @@ use darkfi::{ Error, }; use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction}; -use darkfi_money_contract::model::{Coin, CoinAttributes}; +use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId}; use darkfi_sdk::{ crypto::{ note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, Keypair, PublicKey, DAO_CONTRACT_ID, @@ -101,6 +101,7 @@ fn help(output: &mut Vec) { output.push(String::from("\tunsnooze: Enables the background subscription messages printing")); output.push(String::from("\tscan: Scan the blockchain and parse relevant transactions")); output.push(String::from("\texplorer: Explorer related subcommands")); + output.push(String::from("\talias: Token alias")); } /// Auxiliary function to define the interactive shell completions. @@ -195,7 +196,7 @@ fn completion(buffer: &str, lc: &mut Vec) { return } - if last.starts_with("a") { + if last.starts_with("at") { lc.push(prefix + "attach-fee"); return } @@ -246,7 +247,24 @@ fn completion(buffer: &str, lc: &mut Vec) { return } + if last.starts_with("al") { + lc.push(prefix.clone() + "alias"); + lc.push(prefix.clone() + "alias add"); + lc.push(prefix.clone() + "alias show"); + lc.push(prefix + "alias remove"); + return + } + // Now the catch alls + if last.starts_with("a") { + lc.push(prefix.clone() + "attach-fee"); + lc.push(prefix.clone() + "alias"); + lc.push(prefix.clone() + "alias add"); + lc.push(prefix.clone() + "alias show"); + lc.push(prefix + "alias remove"); + return + } + if last.starts_with("s") { lc.push(prefix.clone() + "spend"); lc.push(prefix.clone() + "subscribe"); @@ -268,32 +286,38 @@ fn hints(buffer: &str) -> Option<(String, i32, bool)> { // Split commands so we always process the last one let commands: Vec<&str> = buffer.split('|').collect(); let last = commands.last().unwrap().trim_start(); + let color = 35; // magenta + let bold = false; match last { - "completions " => Some(("".to_string(), 35, false)), // 35 = magenta - "wallet " => Some(("(initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)".to_string(), 35, false)), - "wallet default-address " => Some(("".to_string(), 35, false)), - "unspend " => Some(("".to_string(), 35, false)), - "transfer " => Some(("[--half-split] [spend_hook] [user_data]".to_string(), 35, false)), - "otc " => Some(("(init|join|inspect|sign)".to_string(), 35, false)), - "otc init " => Some((" ".to_string(), 35, false)), - "dao " => Some(("(create|view|import|update-keys|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)".to_string(), 35, false)), - "dao create " => Some((" ".to_string(), 35, false)), - "dao import " => Some(("".to_string(), 35, false)), - "dao list " => Some(("[name]".to_string(), 35, false)), - "dao balance " => Some(("".to_string(), 35, false)), - "dao mint " => Some(("".to_string(), 35, false)), - "dao propose-transfer " => Some((" [spend-hook] [user-data]".to_string(), 35, false)), - "dao propose-generic" => Some((" [user-data]".to_string(), 35, false)), - "dao proposals " => Some(("".to_string(), 35, false)), - "dao proposal " => Some(("[--(export|mint-proposal)] ".to_string(), 35, false)), - "dao vote " => Some((" [vote-weight]".to_string(), 35, false)), - "dao exec " => Some(("[--early] ".to_string(), 35, false)), - "scan " => Some(("[--reset]".to_string(), 35, false)), - "scan --reset " => Some(("".to_string(), 35, false)), - "explorer " => Some(("(fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks)".to_string(), 35, false)), - "explorer fetch-tx " => Some(("[--encode] ".to_string(), 35, false)), - "explorer txs-history " => Some(("[--encode] [tx-hash]".to_string(), 35, false)), - "explorer scanned-blocks " => Some(("[height]".to_string(), 35, false)), + "completions " => Some(("".to_string(), color, bold)), + "wallet " => Some(("(initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)".to_string(), color, bold)), + "wallet default-address " => Some(("".to_string(), color, bold)), + "unspend " => Some(("".to_string(), color, bold)), + "transfer " => Some(("[--half-split] [spend_hook] [user_data]".to_string(), color, bold)), + "otc " => Some(("(init|join|inspect|sign)".to_string(), color, bold)), + "otc init " => Some((" ".to_string(), color, bold)), + "dao " => Some(("(create|view|import|update-keys|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)".to_string(), color, bold)), + "dao create " => Some((" ".to_string(), color, bold)), + "dao import " => Some(("".to_string(), color, bold)), + "dao list " => Some(("[name]".to_string(), color, bold)), + "dao balance " => Some(("".to_string(), color, bold)), + "dao mint " => Some(("".to_string(), color, bold)), + "dao propose-transfer " => Some((" [spend-hook] [user-data]".to_string(), color, bold)), + "dao propose-generic" => Some((" [user-data]".to_string(), color, bold)), + "dao proposals " => Some(("".to_string(), color, bold)), + "dao proposal " => Some(("[--(export|mint-proposal)] ".to_string(), color, bold)), + "dao vote " => Some((" [vote-weight]".to_string(), color, bold)), + "dao exec " => Some(("[--early] ".to_string(), color, bold)), + "scan " => Some(("[--reset]".to_string(), color, bold)), + "scan --reset " => Some(("".to_string(), color, bold)), + "explorer " => Some(("(fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks)".to_string(), color, bold)), + "explorer fetch-tx " => Some(("[--encode] ".to_string(), color, bold)), + "explorer txs-history " => Some(("[--encode] [tx-hash]".to_string(), color, bold)), + "explorer scanned-blocks " => Some(("[height]".to_string(), color, bold)), + "alias " => Some(("(add|show|remove)".to_string(), color, bold)), + "alias add " => Some((" ".to_string(), color, bold)), + "alias show " => Some(("[-a, --alias ] [-t, --token ]".to_string(), color, bold)), + "alias remove " => Some(("".to_string(), color, bold)), _ => None, } } @@ -480,6 +504,7 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: & .await } "explorer" => handle_explorer(drk, &parts, &input, &mut output).await, + "alias" => handle_alias(drk, &parts, &mut output).await, _ => output.push(format!("Unreconized command: {}", parts[0])), } @@ -695,7 +720,7 @@ async fn handle_wallet_initialize(drk: &DrkPtr, output: &mut Vec) { output.push(format!("Error initializing wallet: {e:?}")); return } - if let Err(e) = lock.initialize_money().await { + if let Err(e) = lock.initialize_money(output).await { output.push(format!("Failed to initialize Money: {e:?}")); return } @@ -1528,7 +1553,7 @@ async fn handle_dao_update_keys( /// Auxiliary function to define the dao list subcommand handling. async fn handle_dao_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 2 || parts.len() != 3 { + if parts.len() != 2 && parts.len() != 3 { output.push(String::from("Malformed `dao list` subcommand")); output.push(String::from("Usage: dao list [name]")); return @@ -1699,7 +1724,7 @@ async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut /// Auxiliary function to define the dao propose generic subcommand handling. async fn handle_dao_propose_generic(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 4 || parts.len() != 5 { + if parts.len() != 4 && parts.len() != 5 { output.push(String::from("Malformed `dao proposal-generic` subcommand")); output.push(String::from("Usage: dao proposal-generic [user-data]")); return @@ -1771,7 +1796,7 @@ async fn handle_dao_proposals(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 3 || parts.len() != 4 { + if parts.len() != 3 && parts.len() != 4 { output.push(String::from("Malformed `dao proposal` subcommand")); output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] ")); return @@ -2102,7 +2127,7 @@ async fn handle_dao_proposal_import( /// Auxiliary function to define the dao vote subcommand handling. async fn handle_dao_vote(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 4 || parts.len() != 5 { + if parts.len() != 4 && parts.len() != 5 { output.push(String::from("Malformed `dao vote` subcommand")); output.push(String::from("Usage: dao vote [vote-weight]")); return @@ -2155,7 +2180,7 @@ async fn handle_dao_vote(drk: &DrkPtr, parts: &[&str], output: &mut Vec) /// Auxiliary function to define the dao exec subcommand handling. async fn handle_dao_exec(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 3 || parts.len() != 4 { + if parts.len() != 3 && parts.len() != 4 { output.push(String::from("Malformed `dao exec` subcommand")); output.push(String::from("Usage: dao exec [--early] ")); return @@ -2415,7 +2440,7 @@ async fn handle_explorer(drk: &DrkPtr, parts: &[&str], input: &[String], output: /// Auxiliary function to define the explorer fetch transaction subcommand handling. async fn handle_explorer_fetch_tx(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 3 || parts.len() != 4 { + if parts.len() != 3 && parts.len() != 4 { output.push(String::from("Malformed `explorer fetch-tx` subcommand")); output.push(String::from("Usage: explorer fetch-tx [--encode] ")); return @@ -2582,7 +2607,7 @@ async fn handle_explorer_clear_reverted(drk: &DrkPtr, parts: &[&str], output: &m /// Auxiliary function to define the explorer scanned blocks subcommand handling. async fn handle_explorer_scanned_blocks(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure - if parts.len() != 2 || parts.len() != 3 { + if parts.len() != 2 && parts.len() != 3 { output.push(String::from("Malformed `explorer scanned-blocks` subcommand")); output.push(String::from("Usage: explorer scanned-blocks [height]")); return @@ -2630,3 +2655,123 @@ async fn handle_explorer_scanned_blocks(drk: &DrkPtr, parts: &[&str], output: &m output.push(format!("{table}")); } } + +/// Auxiliary function to define the alias command handling. +async fn handle_alias(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { + // Check correct command structure + if parts.len() < 2 { + output.push(String::from("Malformed `alias` command")); + output.push(String::from("Usage: alias (add|show|remove)")); + return + } + + // Handle subcommand + match parts[1] { + "add" => handle_alias_add(drk, parts, output).await, + "show" => handle_alias_show(drk, parts, output).await, + "remove" => handle_alias_remove(drk, parts, output).await, + _ => { + output.push(format!("Unreconized alias subcommand: {}", parts[1])); + output.push(String::from("Usage: alias (add|show|remove)")); + } + } +} + +/// Auxiliary function to define the alias add subcommand handling. +async fn handle_alias_add(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { + // Check correct subcommand structure + if parts.len() != 4 { + output.push(String::from("Malformed `alias add` subcommand")); + output.push(String::from("Usage: alias add ")); + return + } + + if parts[2].len() > 5 { + output.push(String::from("Error: Alias exceeds 5 characters")); + return + } + + let token_id = match TokenId::from_str(parts[3]) { + Ok(t) => t, + Err(e) => { + output.push(format!("Invalid Token ID: {e:?}")); + return + } + }; + + if let Err(e) = drk.read().await.add_alias(String::from(parts[2]), token_id, output).await { + output.push(format!("Failed to add alias: {e}")); + } +} + +/// Auxiliary function to define the alias show subcommand handling. +async fn handle_alias_show(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { + // Check correct command structure + if parts.len() != 2 && parts.len() != 4 && parts.len() != 6 { + output.push(String::from("Malformed `alias show` command")); + output.push(String::from("Usage: alias show [-a, --alias ] [-t, --token ]")); + return + } + + let mut alias = None; + let mut token_id = None; + if parts.len() > 2 { + let mut index = 2; + if parts[index] == "-a" || parts[index] == "--alias" { + alias = Some(String::from(parts[index + 1])); + index += 2; + } + + if index < parts.len() && (parts[index] == "-t" || parts[index] == "--token") { + match TokenId::from_str(parts[index + 1]) { + Ok(t) => token_id = Some(t), + Err(e) => { + output.push(format!("Invalid Token ID: {e:?}")); + return + } + }; + index += 2; + } + + // Check alias again in case it was after token + if index < parts.len() && (parts[index] == "-a" || parts[index] == "--alias") { + alias = Some(String::from(parts[index + 1])); + } + } + + let map = match drk.read().await.get_aliases(alias, token_id).await { + Ok(m) => m, + Err(e) => { + output.push(format!("Failed to fetch aliases map: {e:?}")); + return + } + }; + + // Create a prettytable with the new data: + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(row!["Alias", "Token ID"]); + for (alias, token_id) in map.iter() { + table.add_row(row![alias, token_id]); + } + + if table.is_empty() { + output.push(String::from("No aliases found")); + } else { + output.push(format!("{table}")); + } +} + +/// Auxiliary function to define the alias remove subcommand handling. +async fn handle_alias_remove(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { + // Check correct subcommand structure + if parts.len() != 3 { + output.push(String::from("Malformed `alias remove` subcommand")); + output.push(String::from("Usage: alias remove ")); + return + } + + if let Err(e) = drk.read().await.remove_alias(String::from(parts[2]), output).await { + output.push(format!("Failed to remove alias: {e}")); + } +} diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 20f0e53fc..278f3e0c3 100644 --- a/bin/drk/src/main.rs +++ b/bin/drk/src/main.rs @@ -715,10 +715,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { eprintln!("Error initializing wallet: {e:?}"); exit(2); } - if let Err(e) = drk.initialize_money().await { + let mut output = vec![]; + if let Err(e) = drk.initialize_money(&mut output).await { + print_output(&output); eprintln!("Failed to initialize Money: {e:?}"); exit(2); } + print_output(&output); if let Err(e) = drk.initialize_dao().await { eprintln!("Failed to initialize DAO: {e:?}"); exit(2); @@ -2258,10 +2261,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { args.fun, ) .await; - if let Err(e) = drk.add_alias(alias, token_id).await { - eprintln!("Failed to add alias: {e:?}"); + let mut output = vec![]; + if let Err(e) = drk.add_alias(alias, token_id, &mut output).await { + print_output(&output); + eprintln!("Failed to add alias: {e}"); exit(2); } + print_output(&output); Ok(()) } @@ -2316,10 +2322,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { args.fun, ) .await; - if let Err(e) = drk.remove_alias(alias).await { - eprintln!("Failed to remove alias: {e:?}"); + let mut output = vec![]; + if let Err(e) = drk.remove_alias(alias, &mut output).await { + print_output(&output); + eprintln!("Failed to remove alias: {e}"); exit(2); } + print_output(&output); Ok(()) } diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index d132e1577..20864f887 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -120,13 +120,13 @@ pub const BALANCE_BASE10_DECIMALS: usize = 8; impl Drk { /// Initialize wallet with tables for the Money contract. - pub async fn initialize_money(&self) -> WalletDbResult<()> { + pub async fn initialize_money(&self, output: &mut Vec) -> WalletDbResult<()> { // Initialize Money wallet schema let wallet_schema = include_str!("../money.sql"); self.wallet.exec_batch_sql(wallet_schema)?; // Insert DRK alias - self.add_alias("DRK".to_string(), *DARK_TOKEN_ID).await?; + self.add_alias("DRK".to_string(), *DARK_TOKEN_ID, output).await?; Ok(()) } @@ -573,8 +573,13 @@ impl Drk { } /// Create an alias record for provided Token ID. - pub async fn add_alias(&self, alias: String, token_id: TokenId) -> WalletDbResult<()> { - println!("Generating alias {alias} for Token: {token_id}"); + pub async fn add_alias( + &self, + alias: String, + token_id: TokenId, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Generating alias {alias} for Token: {token_id}")); let query = format!( "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);", *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID, @@ -644,8 +649,12 @@ impl Drk { } /// Remove provided alias record from the wallet database. - pub async fn remove_alias(&self, alias: String) -> WalletDbResult<()> { - println!("Removing alias: {alias}"); + pub async fn remove_alias( + &self, + alias: String, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Removing alias: {alias}")); let query = format!( "DELETE FROM {} WHERE {} = ?1;", *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,