Explorar el Código

drk/interactive: previous command output as next command input

skoupidi hace 1 año
padre
commit
5f889aae6f

+ 37 - 23
bin/drk/src/cli_util.rs

@@ -17,7 +17,6 @@
  */
 use std::{
     io::{stdin, Cursor, Read},
-    process::exit,
     str::FromStr,
 };
 
@@ -39,28 +38,37 @@ use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
 pub async fn parse_tx_from_stdin() -> Result<Transaction> {
     let mut buf = String::new();
     stdin().read_to_string(&mut buf)?;
-    let Some(bytes) = base64::decode(buf.trim()) else {
-        eprintln!("Failed to decode transaction");
-        exit(2);
-    };
+    match base64::decode(buf.trim()) {
+        Some(bytes) => Ok(deserialize_async(&bytes).await?),
+        None => Err(Error::ParseFailed("Failed to decode transaction")),
+    }
+}
 
-    Ok(deserialize_async(&bytes).await?)
+/// Auxiliary function to parse a base64 encoded transaction from
+/// provided input or fallback to stdin if its empty.
+pub async fn parse_tx_from_input(input: &[String]) -> Result<Transaction> {
+    match input.len() {
+        0 => parse_tx_from_stdin().await,
+        1 => match base64::decode(input[0].trim()) {
+            Some(bytes) => Ok(deserialize_async(&bytes).await?),
+            None => Err(Error::ParseFailed("Failed to decode transaction")),
+        },
+        _ => Err(Error::ParseFailed("Multiline input provided")),
+    }
 }
 
 /// Auxiliary function to parse provided string into a values pair.
 pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
     let v: Vec<&str> = s.split(':').collect();
     if v.len() != 2 {
-        eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
-        exit(2);
+        return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0"))
     }
 
     let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
     let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
 
     if val0.is_err() || val1.is_err() {
-        eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
-        exit(2);
+        return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0"))
     }
 
     Ok((val0.unwrap(), val1.unwrap()))
@@ -70,22 +78,20 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
 pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
     let v: Vec<&str> = s.split(':').collect();
     if v.len() != 2 {
-        eprintln!("Invalid token pair. Use a pair such as:");
-        eprintln!("WCKD:MLDY");
-        eprintln!("or");
-        eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
-        exit(2);
+        return Err(Error::ParseFailed(
+            "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\
+            A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"
+        ))
     }
 
     let tok0 = drk.get_token(v[0].to_string()).await;
     let tok1 = drk.get_token(v[1].to_string()).await;
 
     if tok0.is_err() || tok1.is_err() {
-        eprintln!("Invalid token pair. Use a pair such as:");
-        eprintln!("WCKD:MLDY");
-        eprintln!("or");
-        eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
-        exit(2);
+        return Err(Error::ParseFailed(
+            "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\
+            A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"
+        ))
     }
 
     Ok((tok0.unwrap(), tok1.unwrap()))
@@ -107,7 +113,7 @@ pub async fn kaching() {
 }
 
 /// Auxiliary function to generate provided shell completions.
-pub fn generate_completions(shell: &str) -> Result<()> {
+pub fn generate_completions(shell: &str) -> Result<String> {
     // Sub-commands
 
     // Interactive
@@ -537,7 +543,15 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         Err(e) => return Err(Error::Custom(e)),
     };
 
-    app.gen_completions_to("./drk", shell, &mut std::io::stdout());
+    let mut buf = vec![];
+    app.gen_completions_to("./drk", shell, &mut buf);
 
-    Ok(())
+    Ok(String::from_utf8(buf)?)
+}
+
+/// Auxiliary function to generate provided shell completions.
+pub fn print_output(buf: &[String]) {
+    for line in buf {
+        println!("{line}");
+    }
 }

+ 44 - 26
bin/drk/src/dao.rs

@@ -1631,24 +1631,26 @@ impl Drk {
     }
 
     /// Reset the DAO Merkle trees in the cache.
-    pub fn reset_dao_trees(&self) -> WalletDbResult<()> {
-        println!("Resetting DAO Merkle trees");
+    pub fn reset_dao_trees(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting DAO Merkle trees"));
         if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_DAO_DAOS) {
-            println!("[reset_dao_trees] Resetting DAO DAOs Merkle tree failed: {e:?}");
+            output.push(format!("[reset_dao_trees] Resetting DAO DAOs Merkle tree failed: {e:?}"));
             return Err(WalletDbError::GenericError)
         }
         if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_DAO_PROPOSALS) {
-            println!("[reset_dao_trees] Resetting DAO Proposals Merkle tree failed: {e:?}");
+            output.push(format!(
+                "[reset_dao_trees] Resetting DAO Proposals Merkle tree failed: {e:?}"
+            ));
             return Err(WalletDbError::GenericError)
         }
-        println!("Successfully reset DAO Merkle trees");
+        output.push(String::from("Successfully reset DAO Merkle trees"));
 
         Ok(())
     }
 
     /// Reset confirmed DAOs in the wallet.
-    pub fn reset_daos(&self) -> WalletDbResult<()> {
-        println!("Resetting DAO confirmations");
+    pub fn reset_daos(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting DAO confirmations"));
         let query = format!(
             "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL;",
             *DAO_DAOS_TABLE,
@@ -1658,15 +1660,19 @@ impl Drk {
             DAO_DAOS_COL_CALL_INDEX,
         );
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully unconfirmed DAOs");
+        output.push(String::from("Successfully unconfirmed DAOs"));
 
         Ok(())
     }
 
     /// Reset confirmed DAOs in the wallet that were minted after
     /// provided height.
-    pub fn unconfirm_daos_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Resetting DAO confirmations after: {height}");
+    pub fn unconfirm_daos_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Resetting DAO confirmations after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} > ?1;",
             *DAO_DAOS_TABLE,
@@ -1677,14 +1683,14 @@ impl Drk {
             DAO_DAOS_COL_MINT_HEIGHT,
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully unconfirmed DAOs");
+        output.push(String::from("Successfully unconfirmed DAOs"));
 
         Ok(())
     }
 
     /// Reset all DAO proposals in the wallet.
-    pub fn reset_dao_proposals(&self) -> WalletDbResult<()> {
-        println!("Resetting DAO proposals confirmations");
+    pub fn reset_dao_proposals(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting DAO proposals confirmations"));
         let query = format!(
             "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL;",
             *DAO_PROPOSALS_TABLE,
@@ -1698,15 +1704,19 @@ impl Drk {
             DAO_PROPOSALS_COL_EXEC_TX_HASH,
         );
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully unconfirmed DAO proposals");
+        output.push(String::from("Successfully unconfirmed DAO proposals"));
 
         Ok(())
     }
 
     /// Reset DAO proposals in the wallet that were minted after
     /// provided height.
-    pub fn unconfirm_dao_proposals_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Resetting DAO proposals confirmations after: {height}");
+    pub fn unconfirm_dao_proposals_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Resetting DAO proposals confirmations after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} > ?1;",
             *DAO_PROPOSALS_TABLE,
@@ -1721,15 +1731,19 @@ impl Drk {
             DAO_PROPOSALS_COL_MINT_HEIGHT,
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully unconfirmed DAO proposals");
+        output.push(String::from("Successfully unconfirmed DAO proposals"));
 
         Ok(())
     }
 
     /// Reset execution information in the wallet for DAO proposals
     /// that were executed after provided height.
-    pub fn unexec_dao_proposals_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Resetting DAO proposals execution information after: {height}");
+    pub fn unexec_dao_proposals_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Resetting DAO proposals execution information after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = NULL, {} = NULL WHERE {} > ?1;",
             *DAO_PROPOSALS_TABLE,
@@ -1738,29 +1752,33 @@ impl Drk {
             DAO_PROPOSALS_COL_EXEC_HEIGHT,
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully reset DAO proposals execution information");
+        output.push(String::from("Successfully reset DAO proposals execution information"));
 
         Ok(())
     }
 
     /// Reset all DAO votes in the wallet.
-    pub fn reset_dao_votes(&self) -> WalletDbResult<()> {
-        println!("Resetting DAO votes");
+    pub fn reset_dao_votes(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting DAO votes"));
         let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE);
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully reset DAO votes");
+        output.push(String::from("Successfully reset DAO votes"));
 
         Ok(())
     }
 
     /// Remove the DAO votes in the wallet that were created after
     /// provided height.
-    pub fn remove_dao_votes_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Removing DAO votes after: {height}");
+    pub fn remove_dao_votes_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Removing DAO votes after: {height}"));
         let query =
             format!("DELETE FROM {} WHERE {} > ?1;", *DAO_VOTES_TABLE, DAO_VOTES_COL_BLOCK_HEIGHT);
         self.wallet.exec_sql(&query, rusqlite::params![height])?;
-        println!("Successfully removed DAO votes");
+        output.push(String::from("Successfully removed DAO votes"));
 
         Ok(())
     }

+ 10 - 6
bin/drk/src/deploy.rs

@@ -85,22 +85,26 @@ impl Drk {
     }
 
     /// Reset all token deploy authorities frozen status in the wallet.
-    pub fn reset_deploy_authorities(&self) -> WalletDbResult<()> {
-        println!("Resetting deploy authorities frozen status");
+    pub fn reset_deploy_authorities(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting deploy authorities frozen status"));
         let query = format!(
             "UPDATE {} SET {} = 0, {} = NULL;",
             *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_FROZEN, DEPLOY_AUTH_COL_FREEZE_HEIGHT
         );
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully reset deploy authorities frozen status");
+        output.push(String::from("Successfully reset deploy authorities frozen status"));
 
         Ok(())
     }
 
     /// Remove deploy authorities frozen status in the wallet that
     /// where frozen after provided height.
-    pub fn unfreeze_deploy_authorities_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Resetting deploy authorities frozen status after: {height}");
+    pub fn unfreeze_deploy_authorities_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Resetting deploy authorities frozen status after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
             *DEPLOY_AUTH_TABLE,
@@ -109,7 +113,7 @@ impl Drk {
             DEPLOY_AUTH_COL_FREEZE_HEIGHT
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully reset deploy authorities frozen status");
+        output.push(String::from("Successfully reset deploy authorities frozen status"));
 
         Ok(())
     }

+ 242 - 211
bin/drk/src/interactive.rs

@@ -47,7 +47,8 @@ use darkfi_serial::{deserialize_async, serialize_async};
 
 use crate::{
     cli_util::{
-        generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair,
+        generate_completions, kaching, parse_token_pair, parse_tx_from_input, parse_value_pair,
+        print_output,
     },
     money::BALANCE_BASE10_DECIMALS,
     rpc::subscribe_blocks,
@@ -293,15 +294,8 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &
         let commands: Vec<&str> = line.split('|').collect();
 
         // Process each command
-        let mut input = vec![];
+        let mut output = vec![];
         for command in commands {
-            // Handle previous command output
-            // TODO: this is examplatory, we will actually use the input
-            // in our commands
-            for line in &input {
-                println!("{line}");
-            }
-
             // Parse command parts
             let parts: Vec<&str> = command.split_whitespace().collect();
             if parts.is_empty() {
@@ -309,20 +303,21 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &
             }
 
             // Handle command
-            let mut output = vec![];
+            let input = output;
+            output = vec![];
             match parts[0] {
                 "help" => help(&mut output),
                 "kaching" => kaching().await,
-                "ping" => handle_ping(drk).await,
-                "completions" => handle_completions(&parts),
-                "wallet" => handle_wallet(drk, &parts).await,
-                "spend" => handle_spend(drk).await,
-                "unspend" => handle_unspend(drk, &parts).await,
-                "transfer" => handle_transfer(drk, &parts).await,
-                "otc" => handle_otc(drk, &parts).await,
-                "attach-fee" => handle_attach_fee(drk).await,
-                "inspect" => handle_inspect().await,
-                "broadcast" => handle_broadcast(drk).await,
+                "ping" => handle_ping(drk, &mut output).await,
+                "completions" => handle_completions(&parts, &mut output),
+                "wallet" => handle_wallet(drk, &parts, &input, &mut output).await,
+                "spend" => handle_spend(drk, &input, &mut output).await,
+                "unspend" => handle_unspend(drk, &parts, &mut output).await,
+                "transfer" => handle_transfer(drk, &parts, &mut output).await,
+                "otc" => handle_otc(drk, &parts, &input, &mut output).await,
+                "attach-fee" => handle_attach_fee(drk, &input, &mut output).await,
+                "inspect" => handle_inspect(&input, &mut output).await,
+                "broadcast" => handle_broadcast(drk, &input, &mut output).await,
                 "subscribe" => {
                     handle_subscribe(
                         drk,
@@ -331,24 +326,23 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &
                         &subscription_task,
                         &shell_sender,
                         ex,
+                        &mut output,
                     )
                     .await
                 }
                 "unsubscribe" => {
-                    handle_unsubscribe(&mut subscription_active, &subscription_task).await
+                    handle_unsubscribe(&mut subscription_active, &subscription_task, &mut output)
+                        .await
                 }
                 "snooze" => snooze_active = true,
                 "unsnooze" => snooze_active = false,
-                "scan" => handle_scan(drk, &subscription_active, &parts).await,
-                _ => println!("Unreconized command: {}", parts[0]),
+                "scan" => handle_scan(drk, &subscription_active, &parts, &mut output).await,
+                _ => output.push(format!("Unreconized command: {}", parts[0])),
             }
-            input = output
         }
 
         // Handle last command output
-        for line in input {
-            println!("{line}");
-        }
+        print_output(&output);
     }
 
     // Stop the subscription task if its active
@@ -461,88 +455,89 @@ async fn listen_for_line(
 }
 
 /// Auxiliary function to define the ping command handling.
-async fn handle_ping(drk: &DrkPtr) {
-    if let Err(e) = drk.read().await.ping().await {
-        println!("Error while executing ping command: {e}")
+async fn handle_ping(drk: &DrkPtr, output: &mut Vec<String>) {
+    if let Err(e) = drk.read().await.ping(output).await {
+        output.push(format!("Error while executing ping command: {e}"))
     }
 }
 
 /// Auxiliary function to define the completions command handling.
-fn handle_completions(parts: &[&str]) {
+fn handle_completions(parts: &[&str], output: &mut Vec<String>) {
     // Check correct command structure
     if parts.len() != 2 {
-        println!("Malformed `completions` command");
-        println!("Usage: completions <shell>");
+        output.push(String::from("Malformed `completions` command"));
+        output.push(String::from("Usage: completions <shell>"));
         return
     }
 
-    if let Err(e) = generate_completions(parts[1]) {
-        println!("Error while executing completions command: {e}")
+    match generate_completions(parts[1]) {
+        Ok(completions) => output.push(completions),
+        Err(e) => output.push(format!("Error while executing completions command: {e}")),
     }
 }
 
 /// Auxiliary function to define the wallet command handling.
-async fn handle_wallet(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_wallet(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
     // Check correct command structure
     if parts.len() < 2 {
-        println!("Malformed `wallet` command");
-        println!("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)");
+        output.push(String::from("Malformed `wallet` command"));
+        output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)"));
         return
     }
 
     // Handle subcommand
     match parts[1] {
-        "initialize" => handle_wallet_initialize(drk).await,
-        "keygen" => handle_wallet_keygen(drk).await,
-        "balance" => handle_wallet_balance(drk).await,
-        "address" => handle_wallet_address(drk).await,
-        "addresses" => handle_wallet_addresses(drk).await,
-        "default-address" => handle_wallet_default_address(drk, parts).await,
-        "secrets" => handle_wallet_secrets(drk).await,
-        "import-secrets" => handle_wallet_import_secrets(drk).await,
-        "tree" => handle_wallet_tree(drk).await,
-        "coins" => handle_wallet_coins(drk).await,
+        "initialize" => handle_wallet_initialize(drk, output).await,
+        "keygen" => handle_wallet_keygen(drk, output).await,
+        "balance" => handle_wallet_balance(drk, output).await,
+        "address" => handle_wallet_address(drk, output).await,
+        "addresses" => handle_wallet_addresses(drk, output).await,
+        "default-address" => handle_wallet_default_address(drk, parts, output).await,
+        "secrets" => handle_wallet_secrets(drk, output).await,
+        "import-secrets" => handle_wallet_import_secrets(drk, input, output).await,
+        "tree" => handle_wallet_tree(drk, output).await,
+        "coins" => handle_wallet_coins(drk, output).await,
         _ => {
-            println!("Unreconized wallet subcommand: {}", parts[1]);
-            println!("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)");
+            output.push(format!("Unreconized wallet subcommand: {}", parts[1]));
+            output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)"));
         }
     }
 }
 
 /// Auxiliary function to define the wallet initialize subcommand handling.
-async fn handle_wallet_initialize(drk: &DrkPtr) {
+async fn handle_wallet_initialize(drk: &DrkPtr, output: &mut Vec<String>) {
     let lock = drk.read().await;
     if let Err(e) = lock.initialize_wallet().await {
-        println!("Error initializing wallet: {e:?}");
+        output.push(format!("Error initializing wallet: {e:?}"));
         return
     }
     if let Err(e) = lock.initialize_money().await {
-        println!("Failed to initialize Money: {e:?}");
+        output.push(format!("Failed to initialize Money: {e:?}"));
         return
     }
     if let Err(e) = lock.initialize_dao().await {
-        println!("Failed to initialize DAO: {e:?}");
+        output.push(format!("Failed to initialize DAO: {e:?}"));
         return
     }
     if let Err(e) = lock.initialize_deployooor() {
-        println!("Failed to initialize Deployooor: {e:?}");
+        output.push(format!("Failed to initialize Deployooor: {e:?}"));
     }
 }
 
 /// Auxiliary function to define the wallet keygen subcommand handling.
-async fn handle_wallet_keygen(drk: &DrkPtr) {
-    if let Err(e) = drk.read().await.money_keygen().await {
-        println!("Failed to generate keypair: {e:?}");
+async fn handle_wallet_keygen(drk: &DrkPtr, output: &mut Vec<String>) {
+    if let Err(e) = drk.read().await.money_keygen(output).await {
+        output.push(format!("Failed to generate keypair: {e:?}"));
     }
 }
 
 /// Auxiliary function to define the wallet balance subcommand handling.
-async fn handle_wallet_balance(drk: &DrkPtr) {
+async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
     let lock = drk.read().await;
     let balmap = match lock.money_balance().await {
         Ok(m) => m,
         Err(e) => {
-            println!("Failed to fetch balances map: {e:?}");
+            output.push(format!("Failed to fetch balances map: {e:?}"));
             return
         }
     };
@@ -550,7 +545,7 @@ async fn handle_wallet_balance(drk: &DrkPtr) {
     let aliases_map = match lock.get_aliases_mapped_by_token().await {
         Ok(m) => m,
         Err(e) => {
-            println!("Failed to fetch aliases map: {e:?}");
+            output.push(format!("Failed to fetch aliases map: {e:?}"));
             return
         }
     };
@@ -569,26 +564,26 @@ async fn handle_wallet_balance(drk: &DrkPtr) {
     }
 
     if table.is_empty() {
-        println!("No unspent balances found");
+        output.push(String::from("No unspent balances found"));
     } else {
-        println!("{table}");
+        output.push(format!("{table}"));
     }
 }
 
 /// Auxiliary function to define the wallet address subcommand handling.
-async fn handle_wallet_address(drk: &DrkPtr) {
+async fn handle_wallet_address(drk: &DrkPtr, output: &mut Vec<String>) {
     match drk.read().await.default_address().await {
-        Ok(address) => println!("{address}"),
-        Err(e) => println!("Failed to fetch default address: {e:?}"),
+        Ok(address) => output.push(format!("{address}")),
+        Err(e) => output.push(format!("Failed to fetch default address: {e:?}")),
     }
 }
 
 /// Auxiliary function to define the wallet addresses subcommand handling.
-async fn handle_wallet_addresses(drk: &DrkPtr) {
+async fn handle_wallet_addresses(drk: &DrkPtr, output: &mut Vec<String>) {
     let addresses = match drk.read().await.addresses().await {
         Ok(a) => a,
         Err(e) => {
-            println!("Failed to fetch addresses: {e:?}");
+            output.push(format!("Failed to fetch addresses: {e:?}"));
             return
         }
     };
@@ -606,90 +601,102 @@ async fn handle_wallet_addresses(drk: &DrkPtr) {
     }
 
     if table.is_empty() {
-        println!("No addresses found");
+        output.push(String::from("No addresses found"));
     } else {
-        println!("{table}");
+        output.push(format!("{table}"));
     }
 }
 
 /// Auxiliary function to define the wallet default address subcommand handling.
-async fn handle_wallet_default_address(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_wallet_default_address(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
     if parts.len() != 3 {
-        println!("Malformed `wallet default-address` subcommand");
-        println!("Usage: wallet default-address <index>");
+        output.push(String::from("Malformed `wallet default-address` subcommand"));
+        output.push(String::from("Usage: wallet default-address <index>"));
         return
     }
 
     let index = match usize::from_str(parts[2]) {
         Ok(i) => i,
         Err(e) => {
-            println!("Invalid address id: {e:?}");
+            output.push(format!("Invalid address id: {e:?}"));
             return
         }
     };
 
     if let Err(e) = drk.read().await.set_default_address(index) {
-        println!("Failed to set default address: {e:?}");
+        output.push(format!("Failed to set default address: {e:?}"));
     }
 }
 
 /// Auxiliary function to define the wallet secrets subcommand handling.
-async fn handle_wallet_secrets(drk: &DrkPtr) {
+async fn handle_wallet_secrets(drk: &DrkPtr, output: &mut Vec<String>) {
     match drk.read().await.get_money_secrets().await {
         Ok(secrets) => {
             for secret in secrets {
-                println!("{secret}");
+                output.push(format!("{secret}"));
             }
         }
-        Err(e) => println!("Failed to fetch secrets: {e:?}"),
+        Err(e) => output.push(format!("Failed to fetch secrets: {e:?}")),
     }
 }
 
 /// Auxiliary function to define the wallet import secrets subcommand handling.
-async fn handle_wallet_import_secrets(drk: &DrkPtr) {
+async fn handle_wallet_import_secrets(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
     let mut secrets = vec![];
-    // TODO: read from a file here not stdin
-    let lines = stdin().lines();
-    for (i, line) in lines.enumerate() {
-        if let Ok(line) = line {
+    // Parse input or read from stdin
+    if input.is_empty() {
+        for (i, line) in stdin().lines().enumerate() {
+            let Ok(line) = line else { continue };
+
             let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
-                println!("Warning: Failed to decode secret on line {i}");
+                output.push(format!("Warning: Failed to decode secret on line {i}"));
                 continue
             };
             let Ok(secret) = deserialize_async(&bytes).await else {
-                println!("Warning: Failed to deserialize secret on line {i}");
+                output.push(format!("Warning: Failed to deserialize secret on line {i}"));
+                continue
+            };
+            secrets.push(secret);
+        }
+    } else {
+        for (i, line) in input.iter().enumerate() {
+            let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
+                output.push(format!("Warning: Failed to decode secret on line {i}"));
+                continue
+            };
+            let Ok(secret) = deserialize_async(&bytes).await else {
+                output.push(format!("Warning: Failed to deserialize secret on line {i}"));
                 continue
             };
             secrets.push(secret);
         }
     }
 
-    match drk.read().await.import_money_secrets(secrets).await {
+    match drk.read().await.import_money_secrets(secrets, output).await {
         Ok(pubkeys) => {
             for key in pubkeys {
-                println!("{key}");
+                output.push(format!("{key}"));
             }
         }
-        Err(e) => println!("Failed to import secrets: {e:?}"),
+        Err(e) => output.push(format!("Failed to import secrets: {e:?}")),
     }
 }
 
 /// Auxiliary function to define the wallet tree subcommand handling.
-async fn handle_wallet_tree(drk: &DrkPtr) {
-    // TODO: write to a file here not stdout
+async fn handle_wallet_tree(drk: &DrkPtr, output: &mut Vec<String>) {
     match drk.read().await.get_money_tree().await {
-        Ok(tree) => println!("{tree:#?}"),
-        Err(e) => println!("Failed to fetch tree: {e:?}"),
+        Ok(tree) => output.push(format!("{tree:#?}")),
+        Err(e) => output.push(format!("Failed to fetch tree: {e:?}")),
     }
 }
 
 /// Auxiliary function to define the wallet coins subcommand handling.
-async fn handle_wallet_coins(drk: &DrkPtr) {
+async fn handle_wallet_coins(drk: &DrkPtr, output: &mut Vec<String>) {
     let lock = drk.read().await;
     let coins = match lock.get_coins(true).await {
         Ok(c) => c,
         Err(e) => {
-            println!("Failed to fetch coins: {e:?}");
+            output.push(format!("Failed to fetch coins: {e:?}"));
             return
         }
     };
@@ -701,7 +708,7 @@ async fn handle_wallet_coins(drk: &DrkPtr) {
     let aliases_map = match lock.get_aliases_mapped_by_token().await {
         Ok(m) => m,
         Err(e) => {
-            println!("Failed to fetch aliases map: {e:?}");
+            output.push(format!("Failed to fetch aliases map: {e:?}"));
             return
         }
     };
@@ -761,37 +768,37 @@ async fn handle_wallet_coins(drk: &DrkPtr) {
         ]);
     }
 
-    println!("{table}");
+    output.push(format!("{table}"));
 }
 
 /// Auxiliary function to define the spend command handling.
-async fn handle_spend(drk: &DrkPtr) {
-    let tx = match parse_tx_from_stdin().await {
+async fn handle_spend(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
+    let tx = match parse_tx_from_input(input).await {
         Ok(t) => t,
         Err(e) => {
-            println!("Error while parsing transaction: {e}");
+            output.push(format!("Error while parsing transaction: {e}"));
             return
         }
     };
 
-    if let Err(e) = drk.read().await.mark_tx_spend(&tx).await {
-        println!("Failed to mark transaction coins as spent: {e}")
+    if let Err(e) = drk.read().await.mark_tx_spend(&tx, output).await {
+        output.push(format!("Failed to mark transaction coins as spent: {e}"))
     }
 }
 
 /// Auxiliary function to define the unspend command handling.
-async fn handle_unspend(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_unspend(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
     // Check correct command structure
     if parts.len() != 2 {
-        println!("Malformed `unspend` command");
-        println!("Usage: unspend <coin>");
+        output.push(String::from("Malformed `unspend` command"));
+        output.push(String::from("Usage: unspend <coin>"));
         return
     }
 
     let bytes = match bs58::decode(&parts[1]).into_vec() {
         Ok(b) => b,
         Err(e) => {
-            println!("Invalid coin: {e}");
+            output.push(format!("Invalid coin: {e}"));
             return
         }
     };
@@ -799,7 +806,7 @@ async fn handle_unspend(drk: &DrkPtr, parts: &[&str]) {
     let bytes: [u8; 32] = match bytes.try_into() {
         Ok(b) => b,
         Err(e) => {
-            println!("Invalid coin: {e:?}");
+            output.push(format!("Invalid coin: {e:?}"));
             return
         }
     };
@@ -807,24 +814,24 @@ async fn handle_unspend(drk: &DrkPtr, parts: &[&str]) {
     let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
         Some(v) => v,
         None => {
-            println!("Invalid coin");
+            output.push(String::from("Invalid coin"));
             return
         }
     };
 
     if let Err(e) = drk.read().await.unspend_coin(&Coin::from(elem)).await {
-        println!("Failed to mark coin as unspent: {e}")
+        output.push(format!("Failed to mark coin as unspent: {e}"))
     }
 }
 
 /// Auxiliary function to define the transfer command handling.
-async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
     // Check correct command structure
     if parts.len() < 4 || parts.len() > 7 {
-        println!("Malformed `transfer` command");
-        println!(
-            "Usage: transfer [--half-split] <amount> <token> <recipient> [spend_hook] [user_data]"
-        );
+        output.push(String::from("Malformed `transfer` command"));
+        output.push(String::from(
+            "Usage: transfer [--half-split] <amount> <token> <recipient> [spend_hook] [user_data]",
+        ));
         return
     }
 
@@ -838,7 +845,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
 
     let amount = String::from(parts[index]);
     if let Err(e) = f64::from_str(&amount) {
-        println!("Invalid amount: {e}");
+        output.push(format!("Invalid amount: {e}"));
         return
     }
     index += 1;
@@ -847,7 +854,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
     let token_id = match lock.get_token(String::from(parts[index])).await {
         Ok(t) => t,
         Err(e) => {
-            println!("Invalid token alias: {e}");
+            output.push(format!("Invalid token alias: {e}"));
             return
         }
     };
@@ -856,7 +863,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
     let rcpt = match PublicKey::from_str(parts[index]) {
         Ok(r) => r,
         Err(e) => {
-            println!("Invalid recipient: {e}");
+            output.push(format!("Invalid recipient: {e}"));
             return
         }
     };
@@ -866,7 +873,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
         match FuncId::from_str(parts[index]) {
             Ok(s) => Some(s),
             Err(e) => {
-                println!("Invalid spend hook: {e}");
+                output.push(format!("Invalid spend hook: {e}"));
                 return
             }
         }
@@ -879,7 +886,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
         let bytes = match bs58::decode(&parts[index]).into_vec() {
             Ok(b) => b,
             Err(e) => {
-                println!("Invalid user data: {e}");
+                output.push(format!("Invalid user data: {e}"));
                 return
             }
         };
@@ -887,7 +894,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
         let bytes: [u8; 32] = match bytes.try_into() {
             Ok(b) => b,
             Err(e) => {
-                println!("Invalid user data: {e:?}");
+                output.push(format!("Invalid user data: {e:?}"));
                 return
             }
         };
@@ -895,7 +902,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
         let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
             Some(v) => v,
             None => {
-                println!("Invalid user data");
+                output.push(String::from("Invalid user data"));
                 return
             }
         };
@@ -905,48 +912,47 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) {
         None
     };
 
-    // TODO: write to a file here not stdout
     match lock.transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split).await {
-        Ok(t) => println!("{}", base64::encode(&serialize_async(&t).await)),
-        Err(e) => println!("Failed to create payment transaction: {e}"),
+        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
+        Err(e) => output.push(format!("Failed to create payment transaction: {e}")),
     }
 }
 
 /// Auxiliary function to define the otc command handling.
-async fn handle_otc(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_otc(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
     // Check correct command structure
     if parts.len() < 2 {
-        println!("Malformed `otc` command");
-        println!("Usage: otc (init|join|inspect|sign)");
+        output.push(String::from("Malformed `otc` command"));
+        output.push(String::from("Usage: otc (init|join|inspect|sign)"));
         return
     }
 
     // Handle subcommand
     match parts[1] {
-        "init" => handle_otc_init(drk, parts).await,
-        "join" => handle_otc_join(drk, parts).await,
-        "inspect" => handle_otc_inspect(drk, parts).await,
-        "sign" => handle_otc_sign(drk, parts).await,
+        "init" => handle_otc_init(drk, parts, output).await,
+        "join" => handle_otc_join(drk, parts, input, output).await,
+        "inspect" => handle_otc_inspect(drk, parts, input, output).await,
+        "sign" => handle_otc_sign(drk, parts, input, output).await,
         _ => {
-            println!("Unreconized OTC subcommand: {}", parts[1]);
-            println!("Usage: otc (init|join|inspect|sign)");
+            output.push(format!("Unreconized OTC subcommand: {}", parts[1]));
+            output.push(String::from("Usage: otc (init|join|inspect|sign)"));
         }
     }
 }
 
 /// Auxiliary function to define the otc init subcommand handling.
-async fn handle_otc_init(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_otc_init(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
     // Check correct subcommand structure
     if parts.len() != 4 {
-        println!("Malformed `otc init` subcommand");
-        println!("Usage: otc init <value_pair> <token_pair>");
+        output.push(String::from("Malformed `otc init` subcommand"));
+        output.push(String::from("Usage: otc init <value_pair> <token_pair>"));
         return
     }
 
     let value_pair = match parse_value_pair(parts[2]) {
         Ok(v) => v,
         Err(e) => {
-            println!("Invalid value pair: {e}");
+            output.push(format!("Invalid value pair: {e}"));
             return
         }
     };
@@ -955,153 +961,174 @@ async fn handle_otc_init(drk: &DrkPtr, parts: &[&str]) {
     let token_pair = match parse_token_pair(&lock, parts[3]).await {
         Ok(t) => t,
         Err(e) => {
-            println!("Invalid token pair: {e}");
+            output.push(format!("Invalid token pair: {e}"));
             return
         }
     };
 
     match lock.init_swap(value_pair, token_pair, None, None, None).await {
-        Ok(half) => println!("{}", base64::encode(&serialize_async(&half).await)),
-        Err(e) => eprintln!("Failed to create swap transaction half: {e}"),
+        Ok(half) => output.push(base64::encode(&serialize_async(&half).await)),
+        Err(e) => output.push(format!("Failed to create swap transaction half: {e}")),
     }
 }
 
 /// Auxiliary function to define the otc join subcommand handling.
-async fn handle_otc_join(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_otc_join(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
     // Check correct subcommand structure
     if parts.len() != 2 {
-        println!("Malformed `otc join` subcommand");
-        println!("Usage: otc join");
+        output.push(String::from("Malformed `otc join` subcommand"));
+        output.push(String::from("Usage: otc join"));
         return
     }
 
-    // TODO: read from a file here not stdin
-    let mut buf = String::new();
-    if let Err(e) = stdin().read_to_string(&mut buf) {
-        println!("Failed to read from stdin: {e}");
-        return
+    // Parse line from input or fallback to stdin if its empty
+    let buf = match input.len() {
+        0 => {
+            let mut buf = String::new();
+            if let Err(e) = stdin().read_to_string(&mut buf) {
+                output.push(format!("Failed to read from stdin: {e}"));
+                return
+            };
+            buf
+        }
+        1 => input[0].clone(),
+        _ => {
+            output.push(String::from("Multiline input provided"));
+            return
+        }
     };
 
     let Some(bytes) = base64::decode(buf.trim()) else {
-        println!("Failed to decode partial swap data");
+        output.push(String::from("Failed to decode partial swap data"));
         return
     };
 
     let partial: PartialSwapData = match deserialize_async(&bytes).await {
         Ok(p) => p,
         Err(e) => {
-            println!("Failed to deserialize partial swap data: {e}");
+            output.push(format!("Failed to deserialize partial swap data: {e}"));
             return
         }
     };
 
     match drk.read().await.join_swap(partial, None, None, None).await {
-        Ok(tx) => println!("{}", base64::encode(&serialize_async(&tx).await)),
-        Err(e) => eprintln!("Failed to create a join swap transaction: {e}"),
+        Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+        Err(e) => output.push(format!("Failed to create a join swap transaction: {e}")),
     }
 }
 
 /// Auxiliary function to define the otc inspect subcommand handling.
-async fn handle_otc_inspect(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_otc_inspect(
+    drk: &DrkPtr,
+    parts: &[&str],
+    input: &[String],
+    output: &mut Vec<String>,
+) {
     // Check correct subcommand structure
     if parts.len() != 2 {
-        println!("Malformed `otc inspect` subcommand");
-        println!("Usage: otc inspect");
+        output.push(String::from("Malformed `otc inspect` subcommand"));
+        output.push(String::from("Usage: otc inspect"));
         return
     }
 
-    // TODO: read from a file here not stdin
-    let mut buf = String::new();
-    if let Err(e) = stdin().read_to_string(&mut buf) {
-        println!("Failed to read from stdin: {e}");
-        return
+    // Parse line from input or fallback to stdin if its empty
+    let buf = match input.len() {
+        0 => {
+            let mut buf = String::new();
+            if let Err(e) = stdin().read_to_string(&mut buf) {
+                output.push(format!("Failed to read from stdin: {e}"));
+                return
+            };
+            buf
+        }
+        1 => input[0].clone(),
+        _ => {
+            output.push(String::from("Multiline input provided"));
+            return
+        }
     };
 
     let Some(bytes) = base64::decode(buf.trim()) else {
-        println!("Failed to decode swap transaction");
+        output.push(String::from("Failed to decode swap transaction"));
         return
     };
 
-    if let Err(e) = drk.read().await.inspect_swap(bytes).await {
-        println!("Failed to inspect swap: {e}");
+    if let Err(e) = drk.read().await.inspect_swap(bytes, output).await {
+        output.push(format!("Failed to inspect swap: {e}"));
     }
 }
 
 /// Auxiliary function to define the otc sign subcommand handling.
-async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str]) {
+async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
     // Check correct subcommand structure
     if parts.len() != 2 {
-        println!("Malformed `otc sign` subcommand");
-        println!("Usage: otc sign");
+        output.push(String::from("Malformed `otc sign` subcommand"));
+        output.push(String::from("Usage: otc sign"));
         return
     }
 
-    // TODO: read from a file here not stdin
-    let mut tx = match parse_tx_from_stdin().await {
+    let mut tx = match parse_tx_from_input(input).await {
         Ok(t) => t,
         Err(e) => {
-            println!("Error while parsing transaction: {e}");
+            output.push(format!("Error while parsing transaction: {e}"));
             return
         }
     };
 
     match drk.read().await.sign_swap(&mut tx).await {
-        Ok(_) => println!("{}", base64::encode(&serialize_async(&tx).await)),
-        Err(e) => println!("Failed to sign joined swap transaction: {e}"),
+        Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
+        Err(e) => output.push(format!("Failed to sign joined swap transaction: {e}")),
     }
 }
 
 /// Auxiliary function to define the attach fee command handling.
-async fn handle_attach_fee(drk: &DrkPtr) {
-    // TODO: read from a file here not stdin
-    let mut tx = match parse_tx_from_stdin().await {
+async fn handle_attach_fee(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
+    let mut tx = match parse_tx_from_input(input).await {
         Ok(t) => t,
         Err(e) => {
-            println!("Error while parsing transaction: {e}");
+            output.push(format!("Error while parsing transaction: {e}"));
             return
         }
     };
 
     match drk.read().await.attach_fee(&mut tx).await {
-        Ok(_) => println!("{}", base64::encode(&serialize_async(&tx).await)),
-        Err(e) => println!("Failed to attach the fee call to the transaction: {e}"),
+        Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
+        Err(e) => output.push(format!("Failed to attach the fee call to the transaction: {e}")),
     }
 }
 
 /// Auxiliary function to define the inspect command handling.
-async fn handle_inspect() {
-    // TODO: read from a file here not stdin
-    match parse_tx_from_stdin().await {
-        Ok(tx) => println!("{tx:#?}"),
-        Err(e) => println!("Error while parsing transaction: {e}"),
+async fn handle_inspect(input: &[String], output: &mut Vec<String>) {
+    match parse_tx_from_input(input).await {
+        Ok(tx) => output.push(format!("{tx:#?}")),
+        Err(e) => output.push(format!("Error while parsing transaction: {e}")),
     }
 }
 
 /// Auxiliary function to define the broadcast command handling.
-async fn handle_broadcast(drk: &DrkPtr) {
-    // TODO: read from a file here not stdin
-    let tx = match parse_tx_from_stdin().await {
+async fn handle_broadcast(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
+    let tx = match parse_tx_from_input(input).await {
         Ok(t) => t,
         Err(e) => {
-            println!("Error while parsing transaction: {e}");
+            output.push(format!("Error while parsing transaction: {e}"));
             return
         }
     };
 
     let lock = drk.read().await;
     if let Err(e) = lock.simulate_tx(&tx).await {
-        println!("Failed to simulate tx: {e}");
+        output.push(format!("Failed to simulate tx: {e}"));
         return
     };
 
-    if let Err(e) = lock.mark_tx_spend(&tx).await {
-        println!("Failed to mark transaction coins as spent: {e}");
+    if let Err(e) = lock.mark_tx_spend(&tx, output).await {
+        output.push(format!("Failed to mark transaction coins as spent: {e}"));
         return
     };
 
-    match lock.broadcast_tx(&tx).await {
-        Ok(txid) => println!("Transaction ID: {txid}"),
-        Err(e) => println!("Failed to broadcast transaction: {e}"),
+    match lock.broadcast_tx(&tx, output).await {
+        Ok(txid) => output.push(format!("Transaction ID: {txid}")),
+        Err(e) => output.push(format!("Failed to broadcast transaction: {e}")),
     }
 }
 
@@ -1113,18 +1140,13 @@ async fn handle_subscribe(
     subscription_task: &StoppableTaskPtr,
     shell_sender: &Sender<Vec<String>>,
     ex: &ExecutorPtr,
+    output: &mut Vec<String>,
 ) {
     if *subscription_active {
-        println!("Subscription is already active!");
+        output.push(String::from("Subscription is already active!"));
         return
     }
 
-    if let Err(e) = drk.read().await.scan_blocks().await {
-        println!("Failed during scanning: {e:?}");
-        return
-    }
-    println!("Finished scanning blockchain");
-
     // Start the subcristion task
     let drk_ = drk.clone();
     let endpoint_ = endpoint.clone();
@@ -1146,9 +1168,13 @@ async fn handle_subscribe(
 }
 
 /// Auxiliary function to define the unsubscribe command handling.
-async fn handle_unsubscribe(subscription_active: &mut bool, subscription_task: &StoppableTaskPtr) {
+async fn handle_unsubscribe(
+    subscription_active: &mut bool,
+    subscription_task: &StoppableTaskPtr,
+    output: &mut Vec<String>,
+) {
     if !*subscription_active {
-        println!("Subscription is already inactive!");
+        output.push(String::from("Subscription is already inactive!"));
         return
     }
     subscription_task.stop().await;
@@ -1156,15 +1182,20 @@ async fn handle_unsubscribe(subscription_active: &mut bool, subscription_task: &
 }
 
 /// Auxiliary function to define the scan command handling.
-async fn handle_scan(drk: &DrkPtr, subscription_active: &bool, parts: &[&str]) {
+async fn handle_scan(
+    drk: &DrkPtr,
+    subscription_active: &bool,
+    parts: &[&str],
+    output: &mut Vec<String>,
+) {
     if *subscription_active {
-        println!("Subscription is already active!");
+        output.push(String::from("Subscription is already active!"));
         return
     }
 
     // Check correct command structure
     if parts.len() != 1 && parts.len() != 3 {
-        println!("Malformed `scan` command");
+        output.push(String::from("Malformed `scan` command"));
         return
     }
 
@@ -1172,28 +1203,28 @@ async fn handle_scan(drk: &DrkPtr, subscription_active: &bool, parts: &[&str]) {
     let lock = drk.read().await;
     if parts.len() == 3 {
         if parts[1] != "--reset" {
-            println!("Malformed `scan` command");
-            println!("Usage: scan --reset <height>");
+            output.push(String::from("Malformed `scan` command"));
+            output.push(String::from("Usage: scan --reset <height>"));
             return
         }
 
         let height = match u32::from_str(parts[2]) {
             Ok(h) => h,
             Err(e) => {
-                println!("Invalid reset height: {e:?}");
+                output.push(format!("Invalid reset height: {e:?}"));
                 return
             }
         };
 
-        if let Err(e) = lock.reset_to_height(height) {
-            println!("Failed during wallet reset: {e:?}");
+        if let Err(e) = lock.reset_to_height(height, output) {
+            output.push(format!("Failed during wallet reset: {e:?}"));
             return
         }
     }
 
-    if let Err(e) = lock.scan_blocks().await {
-        println!("Failed during scanning: {e:?}");
+    if let Err(e) = lock.scan_blocks(output).await {
+        output.push(format!("Failed during scanning: {e:?}"));
         return
     }
-    println!("Finished scanning blockchain");
+    output.push(String::from("Finished scanning blockchain"));
 }

+ 14 - 14
bin/drk/src/lib.rs

@@ -133,20 +133,20 @@ impl Drk {
     }
 
     /// Auxiliary function to completely reset wallet state.
-    pub fn reset(&self) -> WalletDbResult<()> {
-        println!("Resetting full wallet state");
-        self.reset_scanned_blocks()?;
-        self.reset_money_tree()?;
-        self.reset_money_smt()?;
-        self.reset_money_coins()?;
-        self.reset_mint_authorities()?;
-        self.reset_dao_trees()?;
-        self.reset_daos()?;
-        self.reset_dao_proposals()?;
-        self.reset_dao_votes()?;
-        self.reset_deploy_authorities()?;
-        self.reset_tx_history()?;
-        println!("Successfully reset full wallet state");
+    pub fn reset(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting full wallet state"));
+        self.reset_scanned_blocks(output)?;
+        self.reset_money_tree(output)?;
+        self.reset_money_smt(output)?;
+        self.reset_money_coins(output)?;
+        self.reset_mint_authorities(output)?;
+        self.reset_dao_trees(output)?;
+        self.reset_daos(output)?;
+        self.reset_dao_proposals(output)?;
+        self.reset_dao_votes(output)?;
+        self.reset_deploy_authorities(output)?;
+        self.reset_tx_history(output)?;
+        output.push(String::from("Successfully reset full wallet state"));
         Ok(())
     }
 }

+ 31 - 11
bin/drk/src/main.rs

@@ -55,6 +55,7 @@ use darkfi_serial::{deserialize_async, serialize_async};
 use drk::{
     cli_util::{
         generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair,
+        print_output,
     },
     dao::{DaoParams, ProposalRecord},
     interactive::interactive,
@@ -687,11 +688,16 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 args.fun,
             )
             .await;
-            drk.ping().await?;
+            let mut output = vec![];
+            drk.ping(&mut output).await?;
+            print_output(&output);
             drk.stop_rpc_client().await
         }
 
-        Subcmd::Completions { shell } => generate_completions(&shell),
+        Subcmd::Completions { shell } => {
+            println!("{}", generate_completions(&shell)?);
+            Ok(())
+        }
 
         Subcmd::Wallet { command } => {
             let drk = new_wallet(
@@ -725,10 +731,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 }
 
                 WalletSubcmd::Keygen => {
-                    if let Err(e) = drk.money_keygen().await {
+                    let mut output = vec![];
+                    if let Err(e) = drk.money_keygen(&mut output).await {
                         eprintln!("Failed to generate keypair: {e:?}");
                         exit(2);
                     }
+                    print_output(&output);
                 }
 
                 WalletSubcmd::Balance => {
@@ -817,8 +825,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                         }
                     }
 
-                    let pubkeys = match drk.import_money_secrets(secrets).await {
-                        Ok(p) => p,
+                    let mut output = vec![];
+                    let pubkeys = match drk.import_money_secrets(secrets, &mut output).await {
+                        Ok(p) => {
+                            print_output(&output);
+                            p
+                        }
                         Err(e) => {
                             eprintln!("Failed to import secret keys into wallet: {e:?}");
                             exit(2);
@@ -922,10 +934,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             )
             .await;
 
-            if let Err(e) = drk.mark_tx_spend(&tx).await {
+            let mut output = vec![];
+            if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await {
                 eprintln!("Failed to mark transaction coins as spent: {e:?}");
                 exit(2);
             };
+            print_output(&output);
 
             Ok(())
         }
@@ -1119,10 +1133,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     args.fun,
                 )
                 .await;
-                if let Err(e) = drk.inspect_swap(bytes).await {
+                let mut output = vec![];
+                if let Err(e) = drk.inspect_swap(bytes, &mut output).await {
                     eprintln!("Failed to inspect swap: {e:?}");
                     exit(2);
                 };
+                print_output(&output);
 
                 Ok(())
             }
@@ -1953,18 +1969,20 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 exit(2);
             };
 
-            if let Err(e) = drk.mark_tx_spend(&tx).await {
+            let mut output = vec![];
+            if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await {
                 eprintln!("Failed to mark transaction coins as spent: {e:?}");
                 exit(2);
             };
 
-            let txid = match drk.broadcast_tx(&tx).await {
+            let txid = match drk.broadcast_tx(&tx, &mut output).await {
                 Ok(t) => t,
                 Err(e) => {
                     eprintln!("Failed to broadcast transaction: {e:?}");
                     exit(2);
                 }
             };
+            print_output(&output);
 
             println!("Transaction ID: {txid}");
 
@@ -1982,17 +2000,19 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             )
             .await;
 
+            let mut output = vec![];
             if let Some(height) = reset {
-                if let Err(e) = drk.reset_to_height(height) {
+                if let Err(e) = drk.reset_to_height(height, &mut output) {
                     eprintln!("Failed during wallet reset: {e:?}");
                     exit(2);
                 }
             }
 
-            if let Err(e) = drk.scan_blocks().await {
+            if let Err(e) = drk.scan_blocks(&mut output).await {
                 eprintln!("Failed during scanning: {e:?}");
                 exit(2);
             }
+            print_output(&output);
             println!("Finished scanning blockchain");
 
             drk.stop_rpc_client().await

+ 40 - 26
bin/drk/src/money.rs

@@ -132,8 +132,8 @@ impl Drk {
     }
 
     /// Generate a new keypair and place it into the wallet.
-    pub async fn money_keygen(&self) -> WalletDbResult<()> {
-        println!("Generating a new keypair");
+    pub async fn money_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Generating a new keypair"));
 
         // TODO: We might want to have hierarchical deterministic key derivation.
         let keypair = Keypair::random(&mut OsRng);
@@ -155,8 +155,8 @@ impl Drk {
             ],
         )?;
 
-        println!("New address:");
-        println!("{}", keypair.public);
+        output.push(String::from("New address:"));
+        output.push(format!("{}", keypair.public));
 
         Ok(())
     }
@@ -297,7 +297,11 @@ impl Drk {
     /// Import given secret keys into the wallet.
     /// If the key already exists, it will be skipped.
     /// Returns the respective PublicKey objects for the imported keys.
-    pub async fn import_money_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
+    pub async fn import_money_secrets(
+        &self,
+        secrets: Vec<SecretKey>,
+        output: &mut Vec<String>,
+    ) -> Result<Vec<PublicKey>> {
         let existing_secrets = self.get_money_secrets().await?;
 
         let mut ret = Vec::with_capacity(secrets.len());
@@ -305,7 +309,7 @@ impl Drk {
         for secret in secrets {
             // Check if secret already exists
             if existing_secrets.contains(&secret) {
-                println!("Existing key found: {secret}");
+                output.push(format!("Existing key found: {secret}"));
                 continue
             }
 
@@ -1014,7 +1018,7 @@ impl Drk {
     }
 
     /// Mark provided transaction input coins as spent.
-    pub async fn mark_tx_spend(&self, tx: &Transaction) -> Result<()> {
+    pub async fn mark_tx_spend(&self, tx: &Transaction, output: &mut Vec<String>) -> Result<()> {
         // Create a cache of all our own nullifiers
         let mut owncoins_nullifiers = BTreeMap::new();
         for coin in self.get_coins(true).await? {
@@ -1025,13 +1029,13 @@ impl Drk {
         }
 
         let tx_hash = tx.hash().to_string();
-        println!("[mark_tx_spend] Processing transaction: {tx_hash}");
+        output.push(format!("[mark_tx_spend] Processing transaction: {tx_hash}"));
         for (i, call) in tx.calls.iter().enumerate() {
             if call.data.contract_id != *MONEY_CONTRACT_ID {
                 continue
             }
 
-            println!("[mark_tx_spend] Found Money contract in call {i}");
+            output.push(format!("[mark_tx_spend] Found Money contract in call {i}"));
             let nullifiers = self.money_call_nullifiers(call).await?;
             self.mark_spent_coins(None, &owncoins_nullifiers, &nullifiers, &None, &tx_hash)?;
         }
@@ -1101,57 +1105,67 @@ impl Drk {
     }
 
     /// Reset the Money Merkle tree in the cache.
-    pub fn reset_money_tree(&self) -> WalletDbResult<()> {
-        println!("Resetting Money Merkle tree");
+    pub fn reset_money_tree(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting Money Merkle tree"));
         if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_MONEY) {
-            println!("[reset_money_tree] Resetting Money Merkle tree failed: {e:?}");
+            output.push(format!("[reset_money_tree] Resetting Money Merkle tree failed: {e:?}"));
             return Err(WalletDbError::GenericError)
         }
-        println!("Successfully reset Money Merkle tree");
+        output.push(String::from("Successfully reset Money Merkle tree"));
 
         Ok(())
     }
 
     /// Reset the Money nullifiers Sparse Merkle Tree in the cache.
-    pub fn reset_money_smt(&self) -> WalletDbResult<()> {
-        println!("Resetting Money Sparse Merkle tree");
+    pub fn reset_money_smt(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting Money Sparse Merkle tree"));
         if let Err(e) = self.cache.money_smt.clear() {
-            println!("[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e:?}");
+            output.push(format!(
+                "[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e:?}"
+            ));
             return Err(WalletDbError::GenericError)
         }
-        println!("Successfully reset Money Sparse Merkle tree");
+        output.push(String::from("Successfully reset Money Sparse Merkle tree"));
 
         Ok(())
     }
 
     /// Reset the Money coins in the wallet.
-    pub fn reset_money_coins(&self) -> WalletDbResult<()> {
-        println!("Resetting coins");
+    pub fn reset_money_coins(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting coins"));
         let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully reset coins");
+        output.push(String::from("Successfully reset coins"));
 
         Ok(())
     }
 
     /// Remove the Money coins in the wallet that were created after
     /// provided height.
-    pub fn remove_money_coins_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Removing coins after: {height}");
+    pub fn remove_money_coins_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Removing coins after: {height}"));
         let query = format!(
             "DELETE FROM {} WHERE {} > ?1;",
             *MONEY_COINS_TABLE, MONEY_COINS_COL_CREATION_HEIGHT
         );
         self.wallet.exec_sql(&query, rusqlite::params![height])?;
-        println!("Successfully removed coins");
+        output.push(String::from("Successfully removed coins"));
 
         Ok(())
     }
 
     /// Mark the Money coins in the wallet that were spent after
     /// provided height as unspent.
-    pub fn unspent_money_coins_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Unspenting coins after: {height}");
+    pub fn unspent_money_coins_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Unspenting coins after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = 0, {} = NULL, {} = '=' WHERE {} > ?1;",
             *MONEY_COINS_TABLE,
@@ -1161,7 +1175,7 @@ impl Drk {
             MONEY_COINS_COL_SPENT_HEIGHT
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully unspent coins");
+        output.push(String::from("Successfully unspent coins"));
 
         Ok(())
     }

+ 67 - 43
bin/drk/src/rpc.rs

@@ -284,7 +284,7 @@ impl Drk {
     /// Scans the blockchain for wallet relevant transactions,
     /// starting from the last scanned block. If a reorg has happened,
     /// we revert to its previous height and then scan from there.
-    pub async fn scan_blocks(&self) -> WalletDbResult<()> {
+    pub async fn scan_blocks(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
         // Grab last scanned block height
         let (mut height, hash) = self.get_last_scanned_block()?;
 
@@ -294,7 +294,7 @@ impl Drk {
             // Check if block was found
             Err(Error::JsonRpcError((-32121, _))) => None,
             Err(e) => {
-                eprintln!("[scan_blocks] RPC client request failed: {e:?}");
+                output.push(format!("[scan_blocks] RPC client request failed: {e:?}"));
                 return Err(WalletDbError::GenericError)
             }
         };
@@ -302,7 +302,7 @@ impl Drk {
         // Check if a reorg has happened
         if block.is_none() || hash != block.unwrap().hash().to_string() {
             // Find the exact block height the reorg happened
-            println!("A reorg has happened, finding last known common block...");
+            output.push(String::from("A reorg has happened, finding last known common block..."));
             height = height.saturating_sub(1);
             while height != 0 {
                 // Grab our scanned block hash for that height
@@ -314,7 +314,7 @@ impl Drk {
                     // Check if block was found
                     Err(Error::JsonRpcError((-32121, _))) => None,
                     Err(e) => {
-                        eprintln!("[scan_blocks] RPC client request failed: {e:?}");
+                        output.push(format!("[scan_blocks] RPC client request failed: {e:?}"));
                         return Err(WalletDbError::GenericError)
                     }
                 };
@@ -326,8 +326,8 @@ impl Drk {
                 }
 
                 // Reset to its height
-                println!("Last common block found: {height} - {scanned_block_hash}");
-                self.reset_to_height(height)?;
+                output.push(format!("Last common block found: {height} - {scanned_block_hash}"));
+                self.reset_to_height(height, output)?;
                 break
             }
         }
@@ -335,7 +335,7 @@ impl Drk {
         // If last scanned block is genesis(0) we reset,
         // otherwise continue with the next block height.
         if height == 0 {
-            self.reset()?;
+            self.reset(output)?;
         } else {
             height += 1;
         }
@@ -344,22 +344,24 @@ impl Drk {
         let mut scan_cache = match self.scan_cache().await {
             Ok(c) => c,
             Err(e) => {
-                eprintln!("[scan_blocks] Generating scan cache failed: {e:?}");
+                output.push(format!("[scan_blocks] Generating scan cache failed: {e:?}"));
                 return Err(WalletDbError::GenericError)
             }
         };
 
         loop {
             // Grab last confirmed block
-            println!("Requested to scan from block number: {height}");
+            output.push(format!("Requested to scan from block number: {height}"));
             let (last_height, last_hash) = match self.get_last_confirmed_block().await {
                 Ok(last) => last,
                 Err(e) => {
-                    eprintln!("[scan_blocks] RPC client request failed: {e:?}");
+                    output.push(format!("[scan_blocks] RPC client request failed: {e:?}"));
                     return Err(WalletDbError::GenericError)
                 }
             };
-            println!("Last confirmed block reported by darkfid: {last_height} - {last_hash}");
+            output.push(format!(
+                "Last confirmed block reported by darkfid: {last_height} - {last_hash}"
+            ));
 
             // Already scanned last confirmed block
             if height > last_height {
@@ -367,21 +369,21 @@ impl Drk {
             }
 
             while height <= last_height {
-                println!("Requesting block {height}...");
+                output.push(format!("Requesting block {height}..."));
                 let block = match self.get_block_by_height(height).await {
                     Ok(b) => b,
                     Err(e) => {
-                        eprintln!("[scan_blocks] RPC client request failed: {e:?}");
+                        output.push(format!("[scan_blocks] RPC client request failed: {e:?}"));
                         return Err(WalletDbError::GenericError)
                     }
                 };
-                println!("Block {height} received! Scanning block...");
+                output.push(format!("Block {height} received! Scanning block..."));
                 if let Err(e) = self.scan_block(&mut scan_cache, &block).await {
-                    eprintln!("[scan_blocks] Scan block failed: {e:?}");
+                    output.push(format!("[scan_blocks] Scan block failed: {e:?}"));
                     return Err(WalletDbError::GenericError)
                 };
                 for msg in scan_cache.flush_messages() {
-                    println!("{msg}");
+                    output.push(msg);
                 }
                 height += 1;
             }
@@ -416,8 +418,8 @@ impl Drk {
 
     /// Broadcast a given transaction to darkfid and forward onto the network.
     /// Returns the transaction ID upon success.
-    pub async fn broadcast_tx(&self, tx: &Transaction) -> Result<String> {
-        println!("Broadcasting transaction...");
+    pub async fn broadcast_tx(&self, tx: &Transaction, output: &mut Vec<String>) -> Result<String> {
+        output.push(String::from("Broadcasting transaction..."));
 
         let params =
             JsonValue::Array(vec![JsonValue::String(base64::encode(&serialize_async(tx).await))]);
@@ -524,13 +526,13 @@ impl Drk {
     }
 
     /// Auxiliary function to ping configured darkfid daemon for liveness.
-    pub async fn ping(&self) -> Result<()> {
-        println!("Executing ping request to darkfid...");
+    pub async fn ping(&self, output: &mut Vec<String>) -> Result<()> {
+        output.push(String::from("Executing ping request to darkfid..."));
         let latency = Instant::now();
         let rep = self.darkfid_daemon_request("ping", &JsonValue::Array(vec![])).await?;
         let latency = latency.elapsed();
-        println!("Got reply: {rep:?}");
-        println!("Latency: {latency:?}");
+        output.push(format!("Got reply: {rep:?}"));
+        output.push(format!("Latency: {latency:?}"));
         Ok(())
     }
 
@@ -569,17 +571,31 @@ pub async fn subscribe_blocks(
     endpoint: Url,
     ex: &ExecutorPtr,
 ) -> Result<()> {
-    // Grab last confirmed block height
+    // First we do a clean scan
     let lock = drk.read().await;
+    let mut output = vec![];
+    if let Err(e) = lock.scan_blocks(&mut output).await {
+        let err_msg = format!("Failed during scanning: {e:?}");
+        output.push(err_msg.clone());
+        shell_sender.send(output).await?;
+        return Err(Error::Custom(err_msg))
+    }
+    output.push(String::from("Finished scanning blockchain"));
+    shell_sender.send(output).await?;
+
+    // Grab last confirmed block height
     let (last_confirmed_height, _) = lock.get_last_confirmed_block().await?;
 
     // Handle genesis(0) block
     if last_confirmed_height == 0 {
-        if let Err(e) = lock.scan_blocks().await {
-            return Err(Error::DatabaseError(format!(
-                "[subscribe_blocks] Scanning from genesis block failed: {e:?}"
-            )))
+        output = vec![];
+        if let Err(e) = lock.scan_blocks(&mut output).await {
+            let err_msg = format!("[subscribe_blocks] Scanning from genesis block failed: {e:?}");
+            output.push(err_msg.clone());
+            shell_sender.send(output).await?;
+            return Err(Error::Custom(err_msg))
         }
+        shell_sender.send(output).await?;
     }
 
     // Grab last confirmed block again
@@ -589,23 +605,23 @@ pub async fn subscribe_blocks(
     let (mut last_scanned_height, last_scanned_hash) = match lock.get_last_scanned_block() {
         Ok(last) => last,
         Err(e) => {
-            return Err(Error::DatabaseError(format!(
-                "[subscribe_blocks] Retrieving last scanned block failed: {e:?}"
-            )))
+            let err_msg = format!("[subscribe_blocks] Retrieving last scanned block failed: {e:?}");
+            shell_sender.send(vec![err_msg.clone()]).await?;
+            return Err(Error::Custom(err_msg))
         }
     };
 
     // Check if other blocks have been created
     if last_confirmed_height != last_scanned_height || last_confirmed_hash != last_scanned_hash {
+        let err_msg = String::from("[subscribe_blocks] Blockchain not fully scanned");
         shell_sender
             .send(vec![
                 String::from("Warning: Last scanned block is not the last confirmed block."),
                 String::from("You should first fully scan the blockchain, and then subscribe"),
+                err_msg.clone(),
             ])
             .await?;
-        return Err(Error::DatabaseError(
-            "[subscribe_blocks] Blockchain not fully scanned".to_string(),
-        ))
+        return Err(Error::Custom(err_msg))
     }
 
     let mut shell_message =
@@ -644,12 +660,13 @@ pub async fn subscribe_blocks(
     shell_sender.send(shell_message).await?;
     drop(lock);
 
-    let e = loop {
+    let e = 'outer: loop {
         match subscription.receive().await {
             JsonResult::Notification(n) => {
                 let mut shell_message =
                     vec![String::from("Got Block notification from darkfid subscription")];
                 if n.method != "blockchain.subscribe_blocks" {
+                    shell_sender.send(shell_message).await?;
                     break Error::UnexpectedJsonRpc(format!(
                         "Got foreign notification from darkfid: {}",
                         n.method
@@ -658,12 +675,14 @@ pub async fn subscribe_blocks(
 
                 // Verify parameters
                 if !n.params.is_array() {
+                    shell_sender.send(shell_message).await?;
                     break Error::UnexpectedJsonRpc(
                         "Received notification params are not an array".to_string(),
                     )
                 }
                 let params = n.params.get::<Vec<JsonValue>>().unwrap();
                 if params.is_empty() {
+                    shell_sender.send(shell_message).await?;
                     break Error::UnexpectedJsonRpc("Notification parameters are empty".to_string())
                 }
 
@@ -679,10 +698,11 @@ pub async fn subscribe_blocks(
                     let lock = drk.read().await;
                     if block.header.height <= last_scanned_height {
                         let reset_height = block.header.height.saturating_sub(1);
-                        if let Err(e) = lock.reset_to_height(reset_height) {
-                            return Err(Error::DatabaseError(format!(
+                        if let Err(e) = lock.reset_to_height(reset_height, &mut shell_message) {
+                            shell_sender.send(shell_message).await?;
+                            break 'outer Error::Custom(format!(
                                 "[subscribe_blocks] Wallet state reset failed: {e:?}"
-                            )))
+                            ))
                         }
 
                         // Scan genesis again if needed
@@ -690,16 +710,18 @@ pub async fn subscribe_blocks(
                             let genesis = match lock.get_block_by_height(reset_height).await {
                                 Ok(b) => b,
                                 Err(e) => {
-                                    return Err(Error::Custom(format!(
+                                    shell_sender.send(shell_message).await?;
+                                    break 'outer Error::Custom(format!(
                                         "[subscribe_blocks] RPC client request failed: {e:?}"
-                                    )))
+                                    ))
                                 }
                             };
                             let mut scan_cache = lock.scan_cache().await?;
                             if let Err(e) = lock.scan_block(&mut scan_cache, &genesis).await {
-                                return Err(Error::DatabaseError(format!(
+                                shell_sender.send(shell_message).await?;
+                                break 'outer Error::Custom(format!(
                                     "[subscribe_blocks] Scanning block failed: {e:?}"
-                                )))
+                                ))
                             };
                             for msg in scan_cache.flush_messages() {
                                 shell_message.push(msg);
@@ -709,9 +731,10 @@ pub async fn subscribe_blocks(
 
                     let mut scan_cache = lock.scan_cache().await?;
                     if let Err(e) = lock.scan_block(&mut scan_cache, &block).await {
-                        return Err(Error::DatabaseError(format!(
+                        shell_sender.send(shell_message).await?;
+                        break 'outer Error::Custom(format!(
                             "[subscribe_blocks] Scanning block failed: {e:?}"
-                        )))
+                        ))
                     }
                     for msg in scan_cache.flush_messages() {
                         shell_message.push(msg);
@@ -735,5 +758,6 @@ pub async fn subscribe_blocks(
         }
     };
 
+    shell_sender.send(vec![format!("[subscribe_blocks] Subscription loop break: {e}")]).await?;
     Err(e)
 }

+ 34 - 27
bin/drk/src/scanned_blocks.rs

@@ -78,30 +78,34 @@ impl Drk {
     }
 
     /// Reset the scanned blocks information records in the cache.
-    pub fn reset_scanned_blocks(&self) -> WalletDbResult<()> {
-        println!("Resetting scanned blocks");
+    pub fn reset_scanned_blocks(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting scanned blocks"));
         if let Err(e) = self.cache.scanned_blocks.clear() {
-            println!("[reset_scanned_blocks] Resetting scanned blocks tree failed: {e:?}");
+            output.push(format!(
+                "[reset_scanned_blocks] Resetting scanned blocks tree failed: {e:?}"
+            ));
             return Err(WalletDbError::GenericError)
         }
         if let Err(e) = self.cache.state_inverse_diff.clear() {
-            println!("[reset_scanned_blocks] Resetting state inverse diffs tree failed: {e:?}");
+            output.push(format!(
+                "[reset_scanned_blocks] Resetting state inverse diffs tree failed: {e:?}"
+            ));
             return Err(WalletDbError::GenericError)
         }
-        println!("Successfully reset scanned blocks");
+        output.push(String::from("Successfully reset scanned blocks"));
 
         Ok(())
     }
 
     /// Reset state to provided block height.
     /// If genesis block height(0) was provided, perform a full reset.
-    pub fn reset_to_height(&self, height: u32) -> WalletDbResult<()> {
-        println!("Resetting wallet state to block: {height}");
+    pub fn reset_to_height(&self, height: u32, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(format!("Resetting wallet state to block: {height}"));
 
         // If genesis block height(0) was provided,
         // perform a full reset.
         if height == 0 {
-            return self.reset()
+            return self.reset(output)
         }
 
         // Grab last scanned block height
@@ -109,7 +113,9 @@ impl Drk {
 
         // Check if requested height is after it
         if last <= height {
-            println!("Requested block height is greater or equal to last scanned block");
+            output.push(String::from(
+                "Requested block height is greater or equal to last scanned block",
+            ));
             return Ok(())
         }
 
@@ -117,7 +123,7 @@ impl Drk {
         let mut overlay = match CacheOverlay::new(&self.cache) {
             Ok(o) => o,
             Err(e) => {
-                println!("[reset_to_height] Creating cache overlay failed: {e:?}");
+                output.push(format!("[reset_to_height] Creating cache overlay failed: {e:?}"));
                 return Err(WalletDbError::GenericError)
             }
         };
@@ -128,70 +134,71 @@ impl Drk {
             let inverse_diff = match self.cache.get_state_inverse_diff(&height) {
                 Ok(d) => d,
                 Err(e) => {
-                    println!(
+                    output.push(format!(
                         "[reset_to_height] Retrieving state inverse diff from cache failed: {e:?}"
-                    );
+                    ));
                     return Err(WalletDbError::GenericError)
                 }
             };
 
             // Apply it
             if let Err(e) = overlay.0.add_diff(&inverse_diff) {
-                println!("[reset_to_height] Adding state inverse diff to the cache overlay failed: {e:?}");
+                output.push(format!("[reset_to_height] Adding state inverse diff to the cache overlay failed: {e:?}"));
                 return Err(WalletDbError::GenericError)
             }
             if let Err(e) = overlay.0.apply_diff(&inverse_diff) {
-                println!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e:?}");
+                output.push(format!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e:?}"));
                 return Err(WalletDbError::GenericError)
             }
 
             // Remove it
             if let Err(e) = self.cache.state_inverse_diff.remove(height.to_be_bytes()) {
-                println!(
+                output.push(format!(
                     "[reset_to_height] Removing state inverse diff from the cache failed: {e:?}"
-                );
+                ));
                 return Err(WalletDbError::GenericError)
             }
 
             // Flush sled
             if let Err(e) = self.cache.sled_db.flush() {
-                println!("[reset_to_height] Flushing cache sled database failed: {e:?}");
+                output
+                    .push(format!("[reset_to_height] Flushing cache sled database failed: {e:?}"));
                 return Err(WalletDbError::GenericError)
             }
         }
 
         // Remove all wallet coins created after the reset height
-        self.remove_money_coins_after(&height)?;
+        self.remove_money_coins_after(&height, output)?;
 
         // Unspent all wallet coins spent after the reset height
-        self.unspent_money_coins_after(&height)?;
+        self.unspent_money_coins_after(&height, output)?;
 
         // Unfreeze tokens mint authorities frozen after the reset
         // height.
-        self.unfreeze_mint_authorities_after(&height)?;
+        self.unfreeze_mint_authorities_after(&height, output)?;
 
         // Unconfirm DAOs minted after the reset height
-        self.unconfirm_daos_after(&height)?;
+        self.unconfirm_daos_after(&height, output)?;
 
         // Unconfirm DAOs proposals minted after the reset height
-        self.unconfirm_dao_proposals_after(&height)?;
+        self.unconfirm_dao_proposals_after(&height, output)?;
 
         // Reset execution information for DAOs proposals executed
         // after the reset height.
-        self.unexec_dao_proposals_after(&height)?;
+        self.unexec_dao_proposals_after(&height, output)?;
 
         // Remove all DAOs proposals votes created after the reset
         // height.
-        self.remove_dao_votes_after(&height)?;
+        self.remove_dao_votes_after(&height, output)?;
 
         // Unfreeze all contracts frozen after the reset height
-        self.unfreeze_deploy_authorities_after(&height)?;
+        self.unfreeze_deploy_authorities_after(&height, output)?;
 
         // Set reverted status to all transactions executed after reset
         // height.
-        self.revert_transactions_after(&height)?;
+        self.revert_transactions_after(&height, output)?;
 
-        println!("Successfully reset wallet state");
+        output.push(String::from("Successfully reset wallet state"));
         Ok(())
     }
 }

+ 42 - 32
bin/drk/src/swap.rs

@@ -287,11 +287,11 @@ impl Drk {
     }
 
     /// Inspect and verify a given swap (half or full) transaction
-    pub async fn inspect_swap(&self, bytes: Vec<u8>) -> Result<()> {
+    pub async fn inspect_swap(&self, bytes: Vec<u8>, output: &mut Vec<String>) -> Result<()> {
         // First we check if its a partial swap
         if let Ok(partial) = deserialize_async::<PartialSwapData>(&bytes).await {
             // Inspect the PartialSwapData
-            println!("{partial}");
+            output.push(format!("{partial}"));
             return Ok(())
         }
 
@@ -307,23 +307,23 @@ impl Drk {
 
         // We're inspecting a full transaction
         if tx.calls.len() != 1 {
-            eprintln!(
+            output.push(format!(
                 "Found {} contract calls in the transaction, there should be 1",
                 tx.calls.len()
-            );
+            ));
             return insection_error
         }
 
         let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?;
-        println!("Parameters:\n{params:#?}");
+        output.push(format!("Parameters:\n{params:#?}"));
 
         if params.inputs.len() != 2 {
-            eprintln!("Found {} inputs, there should be 2", params.inputs.len());
+            output.push(format!("Found {} inputs, there should be 2", params.inputs.len()));
             return insection_error
         }
 
         if params.outputs.len() != 2 {
-            eprintln!("Found {} outputs, there should be 2", params.outputs.len());
+            output.push(format!("Found {} outputs, there should be 2", params.outputs.len()));
             return insection_error
         }
 
@@ -331,17 +331,18 @@ impl Drk {
         let secret_keys = self.get_money_secrets().await?;
         let mut skey: Option<SecretKey> = None;
         let mut note: Option<MoneyNote> = None;
-        let mut output_idx = 0;
+        let mut param_output_idx = 0;
 
-        for output in &params.outputs {
-            println!("Trying to decrypt note in output {output_idx}");
+        for param_output in &params.outputs {
+            output.push(format!("Trying to decrypt note in output {param_output_idx}"));
 
             for secret in &secret_keys {
-                if let Ok(d_note) = output.note.decrypt::<MoneyNote>(secret) {
+                if let Ok(d_note) = param_output.note.decrypt::<MoneyNote>(secret) {
                     let s: SecretKey = deserialize_async(&d_note.memo).await?;
                     skey = Some(s);
                     note = Some(d_note);
-                    println!("Successfully decrypted and found an ephemeral secret");
+                    output
+                        .push(String::from("Successfully decrypted and found an ephemeral secret"));
                     break
                 }
             }
@@ -350,20 +351,20 @@ impl Drk {
                 break
             }
 
-            output_idx += 1;
+            param_output_idx += 1;
         }
 
         let Some(note) = note else {
-            eprintln!("Error: Could not decrypt notes of either output");
+            output.push(String::from("Error: Could not decrypt notes of either output"));
             return insection_error
         };
 
-        println!(
-            "Output[{output_idx}] value: {} ({})",
+        output.push(format!(
+            "Output[{param_output_idx}] value: {} ({})",
             note.value,
             encode_base10(note.value, BALANCE_BASE10_DECIMALS)
-        );
-        println!("Output[{output_idx}] token ID: {}", note.token_id);
+        ));
+        output.push(format!("Output[{param_output_idx}] token ID: {}", note.token_id));
 
         let skey = skey.unwrap();
         let (pub_x, pub_y) = PublicKey::from_secret(skey).xy();
@@ -375,35 +376,43 @@ impl Drk {
             note.coin_blind.inner(),
         ]));
 
-        if coin == params.outputs[output_idx].coin {
-            println!("Output[{output_idx}] coin matches decrypted note metadata");
+        if coin == params.outputs[param_output_idx].coin {
+            output.push(format!("Output[{param_output_idx}] coin matches decrypted note metadata"));
         } else {
-            eprintln!("Error: Output[{output_idx}] coin does not match note metadata");
+            output.push(format!(
+                "Error: Output[{param_output_idx}] coin does not match note metadata"
+            ));
             return insection_error
         }
 
         let valcom = pedersen_commitment_u64(note.value, note.value_blind);
         let tokcom = poseidon_hash([note.token_id.inner(), note.token_blind.inner()]);
 
-        if valcom != params.outputs[output_idx].value_commit {
-            eprintln!("Error: Output[{output_idx}] value commitment does not match note metadata");
+        if valcom != params.outputs[param_output_idx].value_commit {
+            output.push(format!(
+                "Error: Output[{param_output_idx}] value commitment does not match note metadata"
+            ));
             return insection_error
         }
 
-        if tokcom != params.outputs[output_idx].token_commit {
-            eprintln!("Error: Output[{output_idx}] token commitment does not match note metadata");
+        if tokcom != params.outputs[param_output_idx].token_commit {
+            output.push(format!(
+                "Error: Output[{param_output_idx}] token commitment does not match note metadata"
+            ));
             return insection_error
         }
 
-        println!("Value and token commitments match decrypted note metadata");
+        output.push(String::from("Value and token commitments match decrypted note metadata"));
 
-        // Verify that the output commitments match the other input commitments
-        match output_idx {
+        // Verify that the param output commitments match the other input commitments
+        match param_output_idx {
             0 => {
                 if valcom != params.inputs[1].value_commit ||
                     tokcom != params.inputs[1].token_commit
                 {
-                    eprintln!("Error: Value/Token commits of output[0] do not match input[1]");
+                    output.push(String::from(
+                        "Error: Value/Token commits of output[0] do not match input[1]",
+                    ));
                     return insection_error
                 }
             }
@@ -411,14 +420,16 @@ impl Drk {
                 if valcom != params.inputs[0].value_commit ||
                     tokcom != params.inputs[0].token_commit
                 {
-                    eprintln!("Error: Value/Token commits of output[1] do not match input[0]");
+                    output.push(String::from(
+                        "Error: Value/Token commits of output[1] do not match input[0]",
+                    ));
                     return insection_error
                 }
             }
             _ => unreachable!(),
         }
 
-        println!("Found matching pedersen commitments for outputs and inputs");
+        output.push(String::from("Found matching pedersen commitments for outputs and inputs"));
 
         Ok(())
     }
@@ -475,7 +486,6 @@ impl Drk {
         }
 
         if !found {
-            eprintln!("Error: Failed to decrypt note with any of our secret keys");
             return Err(Error::Custom(
                 "Failed to decrypt note with any of our secret keys".to_string(),
             ))

+ 10 - 6
bin/drk/src/token.rs

@@ -175,22 +175,26 @@ impl Drk {
     }
 
     /// Reset all token mint authorities frozen status in the wallet.
-    pub fn reset_mint_authorities(&self) -> WalletDbResult<()> {
-        println!("Resetting mint authorities frozen status");
+    pub fn reset_mint_authorities(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting mint authorities frozen status"));
         let query = format!(
             "UPDATE {} SET {} = 0, {} = NULL;",
             *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_FREEZE_HEIGHT
         );
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully reset mint authorities frozen status");
+        output.push(String::from("Successfully reset mint authorities frozen status"));
 
         Ok(())
     }
 
     /// Remove token mint authorities frozen status in the wallet that
     /// where frozen after provided height.
-    pub fn unfreeze_mint_authorities_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Resetting mint authorities frozen status after: {height}");
+    pub fn unfreeze_mint_authorities_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Resetting mint authorities frozen status after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
             *MONEY_TOKENS_TABLE,
@@ -199,7 +203,7 @@ impl Drk {
             MONEY_TOKENS_COL_FREEZE_HEIGHT
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully reset mint authorities frozen status");
+        output.push(String::from("Successfully reset mint authorities frozen status"));
 
         Ok(())
     }

+ 10 - 6
bin/drk/src/txs_history.rs

@@ -172,19 +172,23 @@ impl Drk {
     }
 
     /// Reset the transaction history records in the wallet.
-    pub fn reset_tx_history(&self) -> WalletDbResult<()> {
-        println!("Resetting transactions history");
+    pub fn reset_tx_history(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
+        output.push(String::from("Resetting transactions history"));
         let query = format!("DELETE FROM {WALLET_TXS_HISTORY_TABLE};");
         self.wallet.exec_sql(&query, &[])?;
-        println!("Successfully reset transactions history");
+        output.push(String::from("Successfully reset transactions history"));
 
         Ok(())
     }
 
     /// Set reverted status to the transaction history records in the
     /// wallet that where executed after provided height.
-    pub fn revert_transactions_after(&self, height: &u32) -> WalletDbResult<()> {
-        println!("Reverting transactions history after: {height}");
+    pub fn revert_transactions_after(
+        &self,
+        height: &u32,
+        output: &mut Vec<String>,
+    ) -> WalletDbResult<()> {
+        output.push(format!("Reverting transactions history after: {height}"));
         let query = format!(
             "UPDATE {} SET {} = 'Reverted', {} = NULL WHERE {} > ?1;",
             WALLET_TXS_HISTORY_TABLE,
@@ -193,7 +197,7 @@ impl Drk {
             WALLET_TXS_HISTORY_BLOCK_HEIGHT
         );
         self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
-        println!("Successfully reverted transactions history");
+        output.push(String::from("Successfully reverted transactions history"));
 
         Ok(())
     }