ソースを参照

drk/interactive: dao commands added

skoupidi 1 年間 前
コミット
51cb2e9d17
4 ファイル変更1077 行追加46 行削除
  1. 11 11
      bin/drk/src/cli_util.rs
  2. 31 20
      bin/drk/src/dao.rs
  3. 1005 3
      bin/drk/src/interactive.rs
  4. 30 12
      bin/drk/src/main.rs

+ 11 - 11
bin/drk/src/cli_util.rs

@@ -238,17 +238,6 @@ pub fn generate_completions(shell: &str) -> Result<String> {
         .about("OTC atomic swap")
         .subcommands(vec![init, join, inspect, sign]);
 
-    // AttachFee
-    let attach_fee = SubCommand::with_name("attach-fee")
-        .about("Attach the fee call to a transaction given from stdin");
-
-    // Inspect
-    let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
-
-    // Broadcast
-    let broadcast =
-        SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
-
     // DAO
     let proposer_limit = Arg::with_name("proposer-limit")
         .help("The minimum amount of governance tokens needed to open a proposal for this DAO");
@@ -369,6 +358,17 @@ pub fn generate_completions(shell: &str) -> Result<String> {
         spend_hook_cmd,
     ]);
 
+    // AttachFee
+    let attach_fee = SubCommand::with_name("attach-fee")
+        .about("Attach the fee call to a transaction given from stdin");
+
+    // Inspect
+    let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
+
+    // Broadcast
+    let broadcast =
+        SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
+
     // Scan
     let reset = Arg::with_name("reset")
         .long("reset")

+ 31 - 20
bin/drk/src/dao.rs

@@ -1018,7 +1018,7 @@ impl Drk {
     async fn parse_dao_proposal(&self, row: &[Value]) -> Result<ProposalRecord> {
         let Value::Blob(ref proposal_bytes) = row[2] else {
             return Err(Error::ParseFailed(
-                "[get_dao_proposals] Proposal bytes bytes parsing failed",
+                "[parse_dao_proposal] Proposal bytes bytes parsing failed",
             ))
         };
         let proposal = deserialize_async(proposal_bytes).await?;
@@ -1026,7 +1026,7 @@ impl Drk {
         let data = match row[3] {
             Value::Blob(ref data_bytes) => Some(data_bytes.clone()),
             Value::Null => None,
-            _ => return Err(Error::ParseFailed("[get_dao_proposals] Data bytes parsing failed")),
+            _ => return Err(Error::ParseFailed("[parse_dao_proposal] Data bytes parsing failed")),
         };
 
         let leaf_position = match row[4] {
@@ -1036,7 +1036,7 @@ impl Drk {
             Value::Null => None,
             _ => {
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposals] Leaf position bytes parsing failed",
+                    "[parse_dao_proposal] Leaf position bytes parsing failed",
                 ))
             }
         };
@@ -1048,7 +1048,7 @@ impl Drk {
             Value::Null => None,
             _ => {
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposals] Money snapshot tree bytes parsing failed",
+                    "[parse_dao_proposal] Money snapshot tree bytes parsing failed",
                 ))
             }
         };
@@ -1060,7 +1060,7 @@ impl Drk {
             Value::Null => None,
             _ => {
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposals] Nullifiers SMT snapshot bytes parsing failed",
+                    "[parse_dao_proposal] Nullifiers SMT snapshot bytes parsing failed",
                 ))
             }
         };
@@ -1068,12 +1068,14 @@ impl Drk {
         let mint_height = match row[7] {
             Value::Integer(mint_height) => {
                 let Ok(mint_height) = u32::try_from(mint_height) else {
-                    return Err(Error::ParseFailed("[get_dao_proposals] Mint height parsing failed"))
+                    return Err(Error::ParseFailed(
+                        "[parse_dao_proposal] Mint height parsing failed",
+                    ))
                 };
                 Some(mint_height)
             }
             Value::Null => None,
-            _ => return Err(Error::ParseFailed("[get_dao_proposals] Mint height parsing failed")),
+            _ => return Err(Error::ParseFailed("[parse_dao_proposal] Mint height parsing failed")),
         };
 
         let tx_hash = match row[8] {
@@ -1081,7 +1083,7 @@ impl Drk {
             Value::Null => None,
             _ => {
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposals] Transaction hash bytes parsing failed",
+                    "[parse_dao_proposal] Transaction hash bytes parsing failed",
                 ))
             }
         };
@@ -1089,19 +1091,19 @@ impl Drk {
         let call_index = match row[9] {
             Value::Integer(call_index) => {
                 let Ok(call_index) = u8::try_from(call_index) else {
-                    return Err(Error::ParseFailed("[get_dao_proposals] Call index parsing failed"))
+                    return Err(Error::ParseFailed("[parse_dao_proposal] Call index parsing failed"))
                 };
                 Some(call_index)
             }
             Value::Null => None,
-            _ => return Err(Error::ParseFailed("[get_dao_proposals] Call index parsing failed")),
+            _ => return Err(Error::ParseFailed("[parse_dao_proposal] Call index parsing failed")),
         };
 
         let exec_height = match row[10] {
             Value::Integer(exec_height) => {
                 let Ok(exec_height) = u32::try_from(exec_height) else {
                     return Err(Error::ParseFailed(
-                        "[get_dao_proposals] Execution height parsing failed",
+                        "[parse_dao_proposal] Execution height parsing failed",
                     ))
                 };
                 Some(exec_height)
@@ -1109,7 +1111,7 @@ impl Drk {
             Value::Null => None,
             _ => {
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposals] Execution height parsing failed",
+                    "[parse_dao_proposal] Execution height parsing failed",
                 ))
             }
         };
@@ -1121,7 +1123,7 @@ impl Drk {
             Value::Null => None,
             _ => {
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposals] Execution transaction hash bytes parsing failed",
+                    "[parse_dao_proposal] Execution transaction hash bytes parsing failed",
                 ))
             }
         };
@@ -1784,7 +1786,12 @@ impl Drk {
     }
 
     /// Import given DAO params into the wallet with a given name.
-    pub async fn import_dao(&self, name: &str, params: &DaoParams) -> Result<()> {
+    pub async fn import_dao(
+        &self,
+        name: &str,
+        params: &DaoParams,
+        output: &mut Vec<String>,
+    ) -> Result<()> {
         // First let's check if we've imported this DAO with the given name before.
         if self.get_dao_by_name(name).await.is_ok() {
             return Err(Error::DatabaseError(
@@ -1792,7 +1799,7 @@ impl Drk {
             ))
         }
 
-        println!("Importing \"{name}\" DAO into the wallet");
+        output.push(format!("Importing \"{name}\" DAO into the wallet"));
 
         let query = format!(
             "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
@@ -1813,14 +1820,18 @@ impl Drk {
     }
 
     /// Update given DAO params into the wallet, if the corresponding DAO exists.
-    pub async fn update_dao_keys(&self, params: &DaoParams) -> Result<()> {
+    pub async fn update_dao_keys(
+        &self,
+        params: &DaoParams,
+        output: &mut Vec<String>,
+    ) -> Result<()> {
         // Grab the params DAO
         let bulla = params.dao.to_bulla();
         let Ok(dao) = self.get_dao_by_bulla(&bulla).await else {
             return Err(Error::DatabaseError(format!("[import_dao] DAO {bulla} was not found")))
         };
 
-        println!("Updating \"{}\" DAO keys into the wallet", dao.name);
+        output.push(format!("Updating \"{}\" DAO keys into the wallet", dao.name));
 
         let query = format!(
             "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
@@ -1874,16 +1885,16 @@ impl Drk {
 
     /// List DAO(s) imported in the wallet. If a name is given, just print the
     /// metadata for that specific one, if found.
-    pub async fn dao_list(&self, name: &Option<String>) -> Result<()> {
+    pub async fn dao_list(&self, name: &Option<String>, output: &mut Vec<String>) -> Result<()> {
         if let Some(name) = name {
             let dao = self.get_dao_by_name(name).await?;
-            println!("{dao}");
+            output.push(format!("{dao}"));
             return Ok(());
         }
 
         let daos = self.get_daos().await?;
         for (i, dao) in daos.iter().enumerate() {
-            println!("{i}. {}", dao.name);
+            output.push(format!("{i}. {}", dao.name));
         }
 
         Ok(())

+ 1005 - 3
bin/drk/src/interactive.rs

@@ -29,19 +29,27 @@ use linenoise_rs::{
     linenoise_set_completion_callback, linenoise_set_hints_callback, LinenoiseState,
 };
 use prettytable::{format, row, Table};
+use rand::rngs::OsRng;
 use smol::channel::{unbounded, Receiver, Sender};
 use url::Url;
 
 use darkfi::{
     cli_desc,
     system::{msleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
-    util::{encoding::base64, parse::encode_base10, path::expand_path},
+    util::{
+        encoding::base64,
+        parse::{decode_base10, encode_base10},
+        path::expand_path,
+    },
     zk::halo2::Field,
     Error,
 };
-use darkfi_money_contract::model::Coin;
+use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
+use darkfi_money_contract::model::{Coin, CoinAttributes};
 use darkfi_sdk::{
-    crypto::{FuncId, PublicKey},
+    crypto::{
+        note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, Keypair, PublicKey, DAO_CONTRACT_ID,
+    },
     pasta::{group::ff::PrimeField, pallas},
 };
 use darkfi_serial::{deserialize_async, serialize_async};
@@ -51,6 +59,7 @@ use crate::{
         append_or_print, generate_completions, kaching, parse_token_pair, parse_tx_from_input,
         parse_value_pair, print_output,
     },
+    dao::{DaoParams, ProposalRecord},
     money::BALANCE_BASE10_DECIMALS,
     rpc::subscribe_blocks,
     swap::PartialSwapData,
@@ -78,6 +87,7 @@ fn help(output: &mut Vec<String>) {
     output.push(String::from("\tunspend: Unspend a coin"));
     output.push(String::from("\ttransfer: Create a payment transaction"));
     output.push(String::from("\totc: OTC atomic swap"));
+    output.push(String::from("\tdao: DAO functionalities"));
     output
         .push(String::from("\tattach-fee: Attach the fee call to a transaction given from stdin"));
     output.push(String::from("\tinspect: Inspect a transaction from stdin"));
@@ -163,6 +173,26 @@ fn completion(buffer: &str, lc: &mut Vec<String>) {
         return
     }
 
+    if last.starts_with("d") {
+        lc.push(prefix.clone() + "dao");
+        lc.push(prefix.clone() + "dao create");
+        lc.push(prefix.clone() + "dao view");
+        lc.push(prefix.clone() + "dao import");
+        lc.push(prefix.clone() + "dao update-keys");
+        lc.push(prefix.clone() + "dao list");
+        lc.push(prefix.clone() + "dao balance");
+        lc.push(prefix.clone() + "dao mint");
+        lc.push(prefix.clone() + "dao propose-transfer");
+        lc.push(prefix.clone() + "dao propose-generic");
+        lc.push(prefix.clone() + "dao proposals");
+        lc.push(prefix.clone() + "dao proposal");
+        lc.push(prefix.clone() + "dao proposal-import");
+        lc.push(prefix.clone() + "dao vote");
+        lc.push(prefix.clone() + "dao exec");
+        lc.push(prefix + "dao spend-hook");
+        return
+    }
+
     if last.starts_with("a") {
         lc.push(prefix + "attach-fee");
         return
@@ -234,6 +264,19 @@ fn hints(buffer: &str) -> Option<(String, i32, bool)> {
         "transfer " => Some(("[--half-split] <amount> <token> <recipient> [spend_hook] [user_data]".to_string(), 35, false)),
         "otc " => Some(("(init|join|inspect|sign)".to_string(), 35, false)),
         "otc init " => Some(("<value_pair> <token_pair>".to_string(), 35, false)),
+        "dao " => Some(("(create|view|import|update-keys|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)".to_string(), 35, false)),
+        "dao create " => Some(("<proposer-limit> <quorum> <early-exec-quorum> <approval-ratio> <gov-token-id>".to_string(), 35, false)),
+        "dao import " => Some(("<name>".to_string(), 35, false)),
+        "dao list " => Some(("[name]".to_string(), 35, false)),
+        "dao balance " => Some(("<name>".to_string(), 35, false)),
+        "dao mint " => Some(("<name>".to_string(), 35, false)),
+        "dao propose-transfer " => Some(("<name> <duration> <amount> <token> <recipient> [spend-hook] [user-data]".to_string(), 35, false)),
+        "dao propose-generic" => Some(("<name> <duration> [user-data]".to_string(), 35, false)),
+        "dao proposals " => Some(("<name>".to_string(), 35, false)),
+        "dao proposal " => Some(("[--(export|mint-proposal)] <bulla>".to_string(), 35, false)),
+        "dao vote " => Some(("<bulla> <vote> [vote-weight]".to_string(), 35, false)),
+        "dao exec " => Some(("[--early] <bulla>".to_string(), 35, false)),
+        "scan " => Some(("[--reset]".to_string(), 35, false)),
         "scan --reset " => Some(("<height>".to_string(), 35, false)),
         _ => None,
     }
@@ -388,6 +431,7 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &
                 "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,
+                "dao" => handle_dao(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,
@@ -1204,6 +1248,964 @@ async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str], input: &[String], output:
     }
 }
 
+/// Auxiliary function to define the dao command handling.
+async fn handle_dao(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
+    // Check correct command structure
+    if parts.len() < 2 {
+        output.push(String::from("Malformed `dao` command"));
+        output.push(String::from("Usage: dao (create|view|import|update-keys|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)"));
+        return
+    }
+
+    // Handle subcommand
+    match parts[1] {
+        "create" => handle_dao_create(drk, parts, output).await,
+        "view" => handle_dao_view(parts, input, output).await,
+        "import" => handle_dao_import(drk, parts, input, output).await,
+        "update-keys" => handle_dao_update_keys(drk, parts, input, output).await,
+        "list" => handle_dao_list(drk, parts, output).await,
+        "balance" => handle_dao_balance(drk, parts, output).await,
+        "mint" => handle_dao_mint(drk, parts, output).await,
+        "propose-transfer" => handle_dao_propose_transfer(drk, parts, output).await,
+        "propose-generic" => handle_dao_propose_generic(drk, parts, output).await,
+        "proposals" => handle_dao_proposals(drk, parts, output).await,
+        "proposal" => handle_dao_proposal(drk, parts, output).await,
+        "proposal-import" => handle_dao_proposal_import(drk, parts, input, output).await,
+        "vote" => handle_dao_vote(drk, parts, output).await,
+        "exec" => handle_dao_exec(drk, parts, output).await,
+        "spend-hook" => handle_dao_spend_hook(parts, output).await,
+        _ => {
+            output.push(format!("Unreconized DAO subcommand: {}", parts[1]));
+            output.push(String::from("Usage: dao (create|view|import|update-keys|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)"));
+        }
+    }
+}
+
+/// Auxiliary function to define the dao create subcommand handling.
+async fn handle_dao_create(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 7 {
+        output.push(String::from("Malformed `dao create` subcommand"));
+        output.push(String::from("Usage: dao create <proposer-limit> <quorum> <early-exec-quorum> <approval-ratio> <gov-token-id>"));
+        return
+    }
+
+    if let Err(e) = f64::from_str(parts[2]) {
+        output.push(format!("Invalid proposer limit: {e}"));
+        return
+    }
+    let proposer_limit = match decode_base10(parts[2], BALANCE_BASE10_DECIMALS, true) {
+        Ok(p) => p,
+        Err(e) => {
+            output.push(format!("Error while parsing proposer limit: {e}"));
+            return
+        }
+    };
+
+    if let Err(e) = f64::from_str(parts[3]) {
+        output.push(format!("Invalid quorum: {e}"));
+        return
+    }
+    let quorum = match decode_base10(parts[3], BALANCE_BASE10_DECIMALS, true) {
+        Ok(q) => q,
+        Err(e) => {
+            output.push(format!("Error while parsing quorum: {e}"));
+            return
+        }
+    };
+
+    if let Err(e) = f64::from_str(parts[4]) {
+        output.push(format!("Invalid early exec quorum: {e}"));
+        return
+    }
+    let early_exec_quorum = match decode_base10(parts[4], BALANCE_BASE10_DECIMALS, true) {
+        Ok(e) => e,
+        Err(e) => {
+            output.push(format!("Error while parsing early exec quorum: {e}"));
+            return
+        }
+    };
+
+    let approval_ratio = match f64::from_str(parts[5]) {
+        Ok(a) => {
+            if a > 1.0 {
+                output.push(String::from("Error: Approval ratio cannot be >1.0"));
+                return
+            }
+            a
+        }
+        Err(e) => {
+            output.push(format!("Invalid approval ratio: {e}"));
+            return
+        }
+    };
+    let approval_ratio_base = 100_u64;
+    let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
+
+    let gov_token_id = match drk.read().await.get_token(String::from(parts[6])).await {
+        Ok(g) => g,
+        Err(e) => {
+            output.push(format!("Invalid Token ID: {e}"));
+            return
+        }
+    };
+
+    let notes_keypair = Keypair::random(&mut OsRng);
+    let proposer_keypair = Keypair::random(&mut OsRng);
+    let proposals_keypair = Keypair::random(&mut OsRng);
+    let votes_keypair = Keypair::random(&mut OsRng);
+    let exec_keypair = Keypair::random(&mut OsRng);
+    let early_exec_keypair = Keypair::random(&mut OsRng);
+    let bulla_blind = BaseBlind::random(&mut OsRng);
+
+    let params = DaoParams::new(
+        proposer_limit,
+        quorum,
+        early_exec_quorum,
+        approval_ratio_base,
+        approval_ratio_quot,
+        gov_token_id,
+        Some(notes_keypair.secret),
+        notes_keypair.public,
+        Some(proposer_keypair.secret),
+        proposer_keypair.public,
+        Some(proposals_keypair.secret),
+        proposals_keypair.public,
+        Some(votes_keypair.secret),
+        votes_keypair.public,
+        Some(exec_keypair.secret),
+        exec_keypair.public,
+        Some(early_exec_keypair.secret),
+        early_exec_keypair.public,
+        bulla_blind,
+    );
+
+    output.push(params.toml_str());
+}
+
+/// Auxiliary function to define the dao view subcommand handling.
+async fn handle_dao_view(parts: &[&str], input: &[String], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 2 {
+        output.push(String::from("Malformed `dao view` subcommand"));
+        output.push(String::from("Usage: dao view"));
+        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 params = match DaoParams::from_toml_str(&buf) {
+        Ok(p) => p,
+        Err(e) => {
+            output.push(format!("Error while parsing DAO params: {e}"));
+            return
+        }
+    };
+
+    output.push(format!("{params}"));
+}
+
+/// Auxiliary function to define the dao import subcommand handling.
+async fn handle_dao_import(
+    drk: &DrkPtr,
+    parts: &[&str],
+    input: &[String],
+    output: &mut Vec<String>,
+) {
+    // Check correct subcommand structure
+    if parts.len() != 3 {
+        output.push(String::from("Malformed `dao import` subcommand"));
+        output.push(String::from("Usage: dao import <name>"));
+        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 params = match DaoParams::from_toml_str(&buf) {
+        Ok(p) => p,
+        Err(e) => {
+            output.push(format!("Error while parsing DAO params: {e}"));
+            return
+        }
+    };
+
+    if let Err(e) = drk.read().await.import_dao(parts[2], &params, output).await {
+        output.push(format!("Failed to import DAO: {e}"))
+    }
+}
+
+/// Auxiliary function to define the dao update keys subcommand handling.
+async fn handle_dao_update_keys(
+    drk: &DrkPtr,
+    parts: &[&str],
+    input: &[String],
+    output: &mut Vec<String>,
+) {
+    // Check correct subcommand structure
+    if parts.len() != 2 {
+        output.push(String::from("Malformed `dao update-keys` subcommand"));
+        output.push(String::from("Usage: dao update-keys"));
+        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 params = match DaoParams::from_toml_str(&buf) {
+        Ok(p) => p,
+        Err(e) => {
+            output.push(format!("Error while parsing DAO params: {e}"));
+            return
+        }
+    };
+
+    if let Err(e) = drk.read().await.update_dao_keys(&params, output).await {
+        output.push(format!("Failed to update DAO keys: {e}"))
+    }
+}
+
+/// Auxiliary function to define the dao list subcommand handling.
+async fn handle_dao_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 2 || parts.len() != 3 {
+        output.push(String::from("Malformed `dao list` subcommand"));
+        output.push(String::from("Usage: dao list [name]"));
+        return
+    }
+
+    let name = if parts.len() == 3 { Some(String::from(parts[2])) } else { None };
+
+    if let Err(e) = drk.read().await.dao_list(&name, output).await {
+        output.push(format!("Failed to list DAO: {e}"))
+    }
+}
+
+/// Auxiliary function to define the dao balance subcommand handling.
+async fn handle_dao_balance(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 3 {
+        output.push(String::from("Malformed `dao balance` subcommand"));
+        output.push(String::from("Usage: dao balance <name>"));
+        return
+    }
+
+    let lock = drk.read().await;
+    let balmap = match lock.dao_balance(parts[2]).await {
+        Ok(b) => b,
+        Err(e) => {
+            output.push(format!("Failed to fetch DAO balance: {e:?}"));
+            return
+        }
+    };
+
+    let aliases_map = match lock.get_aliases_mapped_by_token().await {
+        Ok(m) => m,
+        Err(e) => {
+            output.push(format!("Failed to fetch aliases map: {e:?}"));
+            return
+        }
+    };
+
+    let mut table = Table::new();
+    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+    table.set_titles(row!["Token ID", "Aliases", "Balance"]);
+    for (token_id, balance) in balmap.iter() {
+        let aliases = match aliases_map.get(token_id) {
+            Some(a) => a,
+            None => "-",
+        };
+
+        table.add_row(row![token_id, aliases, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]);
+    }
+
+    if table.is_empty() {
+        output.push(String::from("No unspent balances found"))
+    } else {
+        output.push(format!("{table}"));
+    }
+}
+
+/// Auxiliary function to define the dao mint subcommand handling.
+async fn handle_dao_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 3 {
+        output.push(String::from("Malformed `dao mint` subcommand"));
+        output.push(String::from("Usage: dao mint <name>"));
+        return
+    }
+
+    match drk.read().await.dao_mint(parts[2]).await {
+        Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+        Err(e) => output.push(format!("Failed to mint DAO: {e}")),
+    }
+}
+
+/// Auxiliary function to define the dao propose transfer subcommand handling.
+async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() < 7 || parts.len() > 9 {
+        output.push(String::from("Malformed `dao proposal-transfer` subcommand"));
+        output.push(String::from("Usage: dao proposal-transfer <name> <duration> <amount> <token> <recipient> [spend-hook] [user-data]"));
+        return
+    }
+
+    let duration = match u64::from_str(parts[3]) {
+        Ok(d) => d,
+        Err(e) => {
+            output.push(format!("Invalid duration: {e}"));
+            return
+        }
+    };
+
+    let amount = String::from(parts[4]);
+    if let Err(e) = f64::from_str(&amount) {
+        output.push(format!("Invalid amount: {e}"));
+        return
+    }
+
+    let lock = drk.read().await;
+    let token_id = match lock.get_token(String::from(parts[5])).await {
+        Ok(t) => t,
+        Err(e) => {
+            output.push(format!("Invalid token alias: {e}"));
+            return
+        }
+    };
+
+    let rcpt = match PublicKey::from_str(parts[6]) {
+        Ok(r) => r,
+        Err(e) => {
+            output.push(format!("Invalid recipient: {e}"));
+            return
+        }
+    };
+
+    let mut index = 7;
+    let spend_hook = if index < parts.len() {
+        match FuncId::from_str(parts[index]) {
+            Ok(s) => Some(s),
+            Err(e) => {
+                output.push(format!("Invalid spend hook: {e}"));
+                return
+            }
+        }
+    } else {
+        None
+    };
+    index += 1;
+
+    let user_data = if index < parts.len() {
+        let bytes = match bs58::decode(&parts[index]).into_vec() {
+            Ok(b) => b,
+            Err(e) => {
+                output.push(format!("Invalid user data: {e}"));
+                return
+            }
+        };
+
+        let bytes: [u8; 32] = match bytes.try_into() {
+            Ok(b) => b,
+            Err(e) => {
+                output.push(format!("Invalid user data: {e:?}"));
+                return
+            }
+        };
+
+        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
+            Some(v) => v,
+            None => {
+                output.push(String::from("Invalid user data"));
+                return
+            }
+        };
+
+        Some(elem)
+    } else {
+        None
+    };
+
+    match drk
+        .read()
+        .await
+        .dao_propose_transfer(parts[2], duration, &amount, token_id, rcpt, spend_hook, user_data)
+        .await
+    {
+        Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
+        Err(e) => output.push(format!("Failed to create DAO transfer proposal: {e}")),
+    }
+}
+
+/// Auxiliary function to define the dao propose generic subcommand handling.
+async fn handle_dao_propose_generic(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 4 || parts.len() != 5 {
+        output.push(String::from("Malformed `dao proposal-generic` subcommand"));
+        output.push(String::from("Usage: dao proposal-generic <name> <duration> [user-data]"));
+        return
+    }
+
+    let duration = match u64::from_str(parts[3]) {
+        Ok(d) => d,
+        Err(e) => {
+            output.push(format!("Invalid duration: {e}"));
+            return
+        }
+    };
+
+    let user_data = if parts.len() == 5 {
+        let bytes = match bs58::decode(&parts[4]).into_vec() {
+            Ok(b) => b,
+            Err(e) => {
+                output.push(format!("Invalid user data: {e}"));
+                return
+            }
+        };
+
+        let bytes: [u8; 32] = match bytes.try_into() {
+            Ok(b) => b,
+            Err(e) => {
+                output.push(format!("Invalid user data: {e:?}"));
+                return
+            }
+        };
+
+        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
+            Some(v) => v,
+            None => {
+                output.push(String::from("Invalid user data"));
+                return
+            }
+        };
+
+        Some(elem)
+    } else {
+        None
+    };
+
+    match drk.read().await.dao_propose_generic(parts[2], duration, user_data).await {
+        Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
+        Err(e) => output.push(format!("Failed to create DAO generic proposal: {e}")),
+    }
+}
+
+/// Auxiliary function to define the dao proposals subcommand handling.
+async fn handle_dao_proposals(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 3 {
+        output.push(String::from("Malformed `dao proposals` subcommand"));
+        output.push(String::from("Usage: dao proposals <name>"));
+        return
+    }
+
+    match drk.read().await.get_dao_proposals(parts[2]).await {
+        Ok(proposals) => {
+            for (i, proposal) in proposals.iter().enumerate() {
+                output.push(format!("{i}. {}", proposal.bulla()));
+            }
+        }
+        Err(e) => output.push(format!("Failed to retrieve DAO proposals: {e}")),
+    }
+}
+
+/// Auxiliary function to define the dao proposal subcommand handling.
+async fn handle_dao_proposal(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 3 || parts.len() != 4 {
+        output.push(String::from("Malformed `dao proposal` subcommand"));
+        output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] <bulla>"));
+        return
+    }
+
+    let mut index = 2;
+    let (export, mint_proposal) = if parts.len() == 4 {
+        index += 1;
+        match parts[index] {
+            "--export" => (true, false),
+            "--mint-proposal" => (false, true),
+            _ => {
+                output.push(String::from("Malformed `dao proposal` subcommand"));
+                output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] <bulla>"));
+                return
+            }
+        }
+    } else {
+        (false, false)
+    };
+
+    let bulla = match DaoProposalBulla::from_str(parts[index]) {
+        Ok(b) => b,
+        Err(e) => {
+            output.push(format!("Invalid proposal bulla: {e}"));
+            return
+        }
+    };
+
+    let lock = drk.read().await;
+    let proposal = match lock.get_dao_proposal_by_bulla(&bulla).await {
+        Ok(p) => p,
+        Err(e) => {
+            output.push(format!("Failed to fetch DAO proposal: {e}"));
+            return
+        }
+    };
+
+    if export {
+        // Retrieve the DAO
+        let dao = match lock.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
+            Ok(d) => d,
+            Err(e) => {
+                output.push(format!("Failed to fetch DAO: {e}"));
+                return
+            }
+        };
+
+        // Encypt the proposal
+        let enc_note =
+            AeadEncryptedNote::encrypt(&proposal, &dao.params.dao.proposals_public_key, &mut OsRng)
+                .unwrap();
+
+        // Export it to base64
+        output.push(base64::encode(&serialize_async(&enc_note).await));
+        return
+    }
+
+    if mint_proposal {
+        // Identify proposal type by its auth calls
+        for call in &proposal.proposal.auth_calls {
+            // We only support transfer right now
+            if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
+                match lock.dao_transfer_proposal_tx(&proposal).await {
+                    Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+                    Err(e) => output.push(format!("Failed to create DAO transfer proposal: {e}")),
+                }
+                return
+            }
+        }
+
+        // If proposal has no auth calls, we consider it a generic one
+        if proposal.proposal.auth_calls.is_empty() {
+            match lock.dao_generic_proposal_tx(&proposal).await {
+                Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+                Err(e) => output.push(format!("Failed to create DAO generic proposal: {e}")),
+            }
+            return
+        }
+
+        output.push(String::from("Unsuported DAO proposal"));
+        return
+    }
+
+    output.push(format!("{proposal}"));
+
+    let mut contract_calls = "\nInvoked contracts:\n".to_string();
+    for call in proposal.proposal.auth_calls {
+        contract_calls.push_str(&format!(
+            "\tContract: {}\n\tFunction: {}\n\tData: ",
+            call.contract_id, call.function_code
+        ));
+
+        if call.auth_data.is_empty() {
+            contract_calls.push_str("-\n");
+            continue;
+        }
+
+        if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
+            // We know that the plaintext data live in the data plaintext vec
+            if proposal.data.is_none() {
+                contract_calls.push_str("-\n");
+                continue;
+            }
+            let coin: CoinAttributes = match deserialize_async(proposal.data.as_ref().unwrap())
+                .await
+            {
+                Ok(c) => c,
+                Err(e) => {
+                    output.push(format!("Failed to deserialize transfer proposal coin data: {e}"));
+                    return
+                }
+            };
+            let spend_hook = if coin.spend_hook == FuncId::none() {
+                "-".to_string()
+            } else {
+                format!("{}", coin.spend_hook)
+            };
+
+            let user_data = if coin.user_data == pallas::Base::ZERO {
+                "-".to_string()
+            } else {
+                format!("{:?}", coin.user_data)
+            };
+
+            contract_calls.push_str(&format!(
+                "\n\t\t{}: {}\n\t\t{}: {} ({})\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\n",
+                "Recipient",
+                coin.public_key,
+                "Amount",
+                coin.value,
+                encode_base10(coin.value, BALANCE_BASE10_DECIMALS),
+                "Token",
+                coin.token_id,
+                "Spend hook",
+                spend_hook,
+                "User data",
+                user_data,
+                "Blind",
+                coin.blind
+            ));
+        }
+    }
+
+    output.push(contract_calls);
+
+    let votes = match lock.get_dao_proposal_votes(&bulla).await {
+        Ok(v) => v,
+        Err(e) => {
+            output.push(format!("Failed to fetch DAO proposal votes: {e}"));
+            return
+        }
+    };
+    let mut total_yes_vote_value = 0;
+    let mut total_no_vote_value = 0;
+    let mut total_all_vote_value = 0;
+    let mut table = Table::new();
+    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+    table.set_titles(row!["Transaction", "Tokens", "Vote"]);
+    for vote in votes {
+        let vote_option = if vote.vote_option {
+            total_yes_vote_value += vote.all_vote_value;
+            "Yes"
+        } else {
+            total_no_vote_value += vote.all_vote_value;
+            "No"
+        };
+        total_all_vote_value += vote.all_vote_value;
+
+        table.add_row(row![
+            vote.tx_hash,
+            encode_base10(vote.all_vote_value, BALANCE_BASE10_DECIMALS),
+            vote_option
+        ]);
+    }
+
+    let outcome = if table.is_empty() {
+        output.push(String::from("Votes: No votes found"));
+        "Unknown"
+    } else {
+        output.push(String::from("Votes:"));
+        output.push(format!("{table}"));
+        output.push(format!(
+            "Total tokens votes: {}",
+            encode_base10(total_all_vote_value, BALANCE_BASE10_DECIMALS)
+        ));
+        let approval_ratio = (total_yes_vote_value as f64 * 100.0) / total_all_vote_value as f64;
+        output.push(format!(
+            "Total tokens Yes votes: {} ({approval_ratio:.2}%)",
+            encode_base10(total_yes_vote_value, BALANCE_BASE10_DECIMALS)
+        ));
+        output.push(format!(
+            "Total tokens No votes: {} ({:.2}%)",
+            encode_base10(total_no_vote_value, BALANCE_BASE10_DECIMALS),
+            (total_no_vote_value as f64 * 100.0) / total_all_vote_value as f64
+        ));
+
+        let dao = match lock.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
+            Ok(d) => d,
+            Err(e) => {
+                output.push(format!("Failed to fetch DAO: {e}"));
+                return
+            }
+        };
+        if total_all_vote_value >= dao.params.dao.quorum &&
+            approval_ratio >=
+                (dao.params.dao.approval_ratio_quot / dao.params.dao.approval_ratio_base)
+                    as f64
+        {
+            "Approved"
+        } else {
+            "Rejected"
+        }
+    };
+
+    if let Some(exec_tx_hash) = proposal.exec_tx_hash {
+        output.push(format!("Proposal was executed on transaction: {exec_tx_hash}"));
+        return
+    }
+
+    // Retrieve next block height and current block time target,
+    // to compute their window.
+    let next_block_height = match lock.get_next_block_height().await {
+        Ok(n) => n,
+        Err(e) => {
+            output.push(format!("Failed to fetch next block height: {e}"));
+            return
+        }
+    };
+    let block_target = match lock.get_block_target().await {
+        Ok(b) => b,
+        Err(e) => {
+            output.push(format!("Failed to fetch block target: {e}"));
+            return
+        }
+    };
+    let current_window = blockwindow(next_block_height, block_target);
+    let end_time = proposal.proposal.creation_blockwindow + proposal.proposal.duration_blockwindows;
+    let (voting_status, proposal_status_message) = if current_window < end_time {
+        ("Ongoing", format!("Current proposal outcome: {outcome}"))
+    } else {
+        ("Concluded", format!("Proposal outcome: {outcome}"))
+    };
+    output.push(format!("Voting status: {voting_status}"));
+    output.push(proposal_status_message);
+}
+
+/// Auxiliary function to define the dao proposal import subcommand handling.
+async fn handle_dao_proposal_import(
+    drk: &DrkPtr,
+    parts: &[&str],
+    input: &[String],
+    output: &mut Vec<String>,
+) {
+    // Check correct subcommand structure
+    if parts.len() != 2 {
+        output.push(String::from("Malformed `dao proposal-import` subcommand"));
+        output.push(String::from("Usage: dao proposal-import"));
+        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 {
+        output.push(String::from("Failed to decode encrypted proposal data"));
+        return
+    };
+
+    let encrypted_proposal: AeadEncryptedNote = match deserialize_async(&bytes).await {
+        Ok(e) => e,
+        Err(e) => {
+            output.push(format!("Failed to deserialize encrypted proposal data: {e}"));
+            return
+        }
+    };
+
+    let lock = drk.read().await;
+    let daos = match lock.get_daos().await {
+        Ok(d) => d,
+        Err(e) => {
+            output.push(format!("Failed to retrieve DAOs: {e}"));
+            return
+        }
+    };
+
+    for dao in &daos {
+        // Check if we have the proposals key
+        let Some(proposals_secret_key) = dao.params.proposals_secret_key else { continue };
+
+        // Try to decrypt the proposal
+        let Ok(proposal) = encrypted_proposal.decrypt::<ProposalRecord>(&proposals_secret_key)
+        else {
+            continue
+        };
+
+        let proposal = match lock.get_dao_proposal_by_bulla(&proposal.bulla()).await {
+            Ok(p) => {
+                let mut our_proposal = p;
+                our_proposal.data = proposal.data;
+                our_proposal
+            }
+            Err(_) => proposal,
+        };
+
+        if let Err(e) = lock.put_dao_proposal(&proposal).await {
+            output.push(format!("Failed to put DAO proposal: {e}"));
+        }
+    }
+
+    output.push(String::from("Couldn't decrypt the proposal with out DAO keys"));
+}
+
+/// Auxiliary function to define the dao vote subcommand handling.
+async fn handle_dao_vote(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 4 || parts.len() != 5 {
+        output.push(String::from("Malformed `dao vote` subcommand"));
+        output.push(String::from("Usage: dao vote <bulla> <vote> [vote-weight]"));
+        return
+    }
+
+    let bulla = match DaoProposalBulla::from_str(parts[2]) {
+        Ok(b) => b,
+        Err(e) => {
+            output.push(format!("Invalid proposal bulla: {e}"));
+            return
+        }
+    };
+
+    let vote = match u8::from_str(parts[3]) {
+        Ok(v) => {
+            if v > 1 {
+                output.push(String::from("Vote can be either 0 (NO) or 1 (YES)"));
+                return
+            }
+            v != 0
+        }
+        Err(e) => {
+            output.push(format!("Invalid vote: {e}"));
+            return
+        }
+    };
+
+    let weight = if parts.len() == 5 {
+        if let Err(e) = f64::from_str(parts[4]) {
+            output.push(format!("Invalid vote weight: {e}"));
+            return
+        }
+        match decode_base10(parts[4], BALANCE_BASE10_DECIMALS, true) {
+            Ok(w) => Some(w),
+            Err(e) => {
+                output.push(format!("Error while parsing vote weight: {e}"));
+                return
+            }
+        }
+    } else {
+        None
+    };
+
+    match drk.read().await.dao_vote(&bulla, vote, weight).await {
+        Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+        Err(e) => output.push(format!("Failed to create DAO Vote transaction: {e}")),
+    }
+}
+
+/// Auxiliary function to define the dao exec subcommand handling.
+async fn handle_dao_exec(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 3 || parts.len() != 4 {
+        output.push(String::from("Malformed `dao exec` subcommand"));
+        output.push(String::from("Usage: dao exec [--early] <bulla>"));
+        return
+    }
+
+    let mut index = 1;
+    let mut early = false;
+    if parts[index] == "--early" {
+        early = true;
+        index += 1;
+    }
+
+    let bulla = match DaoProposalBulla::from_str(parts[index]) {
+        Ok(b) => b,
+        Err(e) => {
+            output.push(format!("Invalid proposal bulla: {e}"));
+            return
+        }
+    };
+
+    let lock = drk.read().await;
+    let proposal = match lock.get_dao_proposal_by_bulla(&bulla).await {
+        Ok(p) => p,
+        Err(e) => {
+            output.push(format!("Failed to fetch DAO proposal: {e}"));
+            return
+        }
+    };
+
+    // Identify proposal type by its auth calls
+    for call in &proposal.proposal.auth_calls {
+        // We only support transfer right now
+        if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
+            match lock.dao_exec_transfer(&proposal, early).await {
+                Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+                Err(e) => output.push(format!("Failed to execute DAO transfer proposal: {e}")),
+            };
+            return
+        }
+    }
+
+    // If proposal has no auth calls, we consider it a generic one
+    if proposal.proposal.auth_calls.is_empty() {
+        match lock.dao_exec_generic(&proposal, early).await {
+            Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
+            Err(e) => output.push(format!("Failed to execute DAO generic proposal: {e}")),
+        };
+        return
+    }
+
+    output.push(String::from("Unsuported DAO proposal"));
+}
+
+/// Auxiliary function to define the dao spent hook subcommand handling.
+async fn handle_dao_spend_hook(parts: &[&str], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 2 {
+        output.push(String::from("Malformed `dao spent-hook` subcommand"));
+        output.push(String::from("Usage: dao spent-hook"));
+        return
+    }
+
+    let spend_hook =
+        FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }.to_func_id();
+    output.push(format!("{spend_hook}"));
+}
+
 /// Auxiliary function to define the attach fee command handling.
 async fn handle_attach_fee(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
     let mut tx = match parse_tx_from_input(input).await {

+ 30 - 12
bin/drk/src/main.rs

@@ -163,6 +163,13 @@ enum Subcmd {
         command: OtcSubcmd,
     },
 
+    /// DAO functionalities
+    Dao {
+        #[structopt(subcommand)]
+        /// Sub command to execute
+        command: DaoSubcmd,
+    },
+
     /// Attach the fee call to a transaction given from stdin
     AttachFee,
 
@@ -172,13 +179,6 @@ enum Subcmd {
     /// Read a transaction from stdin and broadcast it
     Broadcast,
 
-    /// DAO functionalities
-    Dao {
-        #[structopt(subcommand)]
-        /// Sub command to execute
-        command: DaoSubcmd,
-    },
-
     /// Scan the blockchain and parse relevant transactions
     Scan {
         #[structopt(long)]
@@ -689,7 +689,10 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             )
             .await;
             let mut output = vec![];
-            drk.ping(&mut output).await?;
+            if let Err(e) = drk.ping(&mut output).await {
+                print_output(&output);
+                return Err(e)
+            };
             print_output(&output);
             drk.stop_rpc_client().await
         }
@@ -733,6 +736,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 WalletSubcmd::Keygen => {
                     let mut output = vec![];
                     if let Err(e) = drk.money_keygen(&mut output).await {
+                        print_output(&output);
                         eprintln!("Failed to generate keypair: {e:?}");
                         exit(2);
                     }
@@ -832,6 +836,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                             p
                         }
                         Err(e) => {
+                            print_output(&output);
                             eprintln!("Failed to import secret keys into wallet: {e:?}");
                             exit(2);
                         }
@@ -936,6 +941,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             let mut output = vec![];
             if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await {
+                print_output(&output);
                 eprintln!("Failed to mark transaction coins as spent: {e:?}");
                 exit(2);
             };
@@ -1135,6 +1141,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 .await;
                 let mut output = vec![];
                 if let Err(e) = drk.inspect_swap(bytes, &mut output).await {
+                    print_output(&output);
                     eprintln!("Failed to inspect swap: {e:?}");
                     exit(2);
                 };
@@ -1274,10 +1281,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     args.fun,
                 )
                 .await;
-                if let Err(e) = drk.import_dao(&name, &params).await {
+                let mut output = vec![];
+                if let Err(e) = drk.import_dao(&name, &params, &mut output).await {
+                    print_output(&output);
                     eprintln!("Failed to import DAO: {e:?}");
                     exit(2);
                 }
+                print_output(&output);
 
                 Ok(())
             }
@@ -1296,10 +1306,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     args.fun,
                 )
                 .await;
-                if let Err(e) = drk.update_dao_keys(&params).await {
+                let mut output = vec![];
+                if let Err(e) = drk.update_dao_keys(&params, &mut output).await {
+                    print_output(&output);
                     eprintln!("Failed to update DAO keys: {e:?}");
                     exit(2);
                 }
+                print_output(&output);
 
                 Ok(())
             }
@@ -1314,10 +1327,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     args.fun,
                 )
                 .await;
-                if let Err(e) = drk.dao_list(&name).await {
+                let mut output = vec![];
+                if let Err(e) = drk.dao_list(&name, &mut output).await {
+                    print_output(&output);
                     eprintln!("Failed to list DAO: {e:?}");
                     exit(2);
                 }
+                print_output(&output);
 
                 Ok(())
             }
@@ -1521,7 +1537,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let proposal = match drk.dao_propose_generic(&name, duration, user_data).await {
                     Ok(p) => p,
                     Err(e) => {
-                        eprintln!("Failed to create DAO transfer proposal: {e:?}");
+                        eprintln!("Failed to create DAO generic proposal: {e:?}");
                         exit(2);
                     }
                 };
@@ -1971,6 +1987,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             let mut output = vec![];
             if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await {
+                print_output(&output);
                 eprintln!("Failed to mark transaction coins as spent: {e:?}");
                 exit(2);
             };
@@ -1978,6 +1995,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             let txid = match drk.broadcast_tx(&tx, &mut output).await {
                 Ok(t) => t,
                 Err(e) => {
+                    print_output(&output);
                     eprintln!("Failed to broadcast transaction: {e:?}");
                     exit(2);
                 }