/ bin / drk / src / interactive.rs
interactive.rs
   1  /* This file is part of DarkFi (https://dark.fi)
   2   *
   3   * Copyright (C) 2020-2025 Dyne.org foundation
   4   *
   5   * This program is free software: you can redistribute it and/or modify
   6   * it under the terms of the GNU Affero General Public License as
   7   * published by the Free Software Foundation, either version 3 of the
   8   * License, or (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13   * GNU Affero General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU Affero General Public License
  16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  17   */
  18  
  19  use std::{
  20      fs::{File, OpenOptions},
  21      io::{stdin, BufRead, BufReader, ErrorKind, Read, Seek, SeekFrom, Write},
  22      str::FromStr,
  23  };
  24  
  25  use futures::{select, FutureExt};
  26  use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
  27  use linenoise_rs::{
  28      linenoise_history_add, linenoise_history_load, linenoise_history_save,
  29      linenoise_set_completion_callback, linenoise_set_hints_callback, LinenoiseState,
  30  };
  31  use prettytable::{format, row, Table};
  32  use rand::rngs::OsRng;
  33  use smol::channel::{unbounded, Receiver, Sender};
  34  use url::Url;
  35  
  36  use darkfi::{
  37      cli_desc,
  38      system::{msleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
  39      util::{
  40          encoding::base64,
  41          parse::{decode_base10, encode_base10},
  42          path::expand_path,
  43      },
  44      zk::halo2::Field,
  45      Error,
  46  };
  47  use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
  48  use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
  49  use darkfi_sdk::{
  50      crypto::{
  51          note::AeadEncryptedNote, BaseBlind, ContractId, FuncId, FuncRef, Keypair, PublicKey,
  52          SecretKey, DAO_CONTRACT_ID,
  53      },
  54      pasta::{group::ff::PrimeField, pallas},
  55      tx::TransactionHash,
  56  };
  57  use darkfi_serial::{deserialize_async, serialize_async};
  58  
  59  use crate::{
  60      cli_util::{
  61          append_or_print, generate_completions, kaching, parse_token_pair, parse_tx_from_input,
  62          parse_value_pair, print_output,
  63      },
  64      dao::{DaoParams, ProposalRecord},
  65      money::BALANCE_BASE10_DECIMALS,
  66      rpc::subscribe_blocks,
  67      swap::PartialSwapData,
  68      DrkPtr,
  69  };
  70  
  71  // TODO:
  72  //  1. Create a transactions cache in the wallet db, so you can use it to handle them.
  73  
  74  /// Auxiliary function to print the help message.
  75  fn help(output: &mut Vec<String>) {
  76      output.push(String::from(cli_desc!()));
  77      output.push(String::from("Commands:"));
  78      output.push(String::from("\thelp: Prints the help message"));
  79      output.push(String::from("\tkaching: Fun"));
  80      output.push(String::from("\tping: Send a ping request to the darkfid RPC endpoint"));
  81      output.push(String::from(
  82          "\tcompletions: Generate a SHELL completion script and print to stdout",
  83      ));
  84      output.push(String::from("\twallet: Wallet operations"));
  85      output.push(String::from(
  86          "\tspend: Read a transaction from stdin and mark its input coins as spent",
  87      ));
  88      output.push(String::from("\tunspend: Unspend a coin"));
  89      output.push(String::from("\ttransfer: Create a payment transaction"));
  90      output.push(String::from("\totc: OTC atomic swap"));
  91      output.push(String::from("\tdao: DAO functionalities"));
  92      output
  93          .push(String::from("\tattach-fee: Attach the fee call to a transaction given from stdin"));
  94      output.push(String::from("\tinspect: Inspect a transaction from stdin"));
  95      output.push(String::from("\tbroadcast: Read a transaction from stdin and broadcast it"));
  96      output.push(String::from(
  97          "\tsubscribe: Perform a scan and then subscribe to darkfid to listen for incoming blocks",
  98      ));
  99      output.push(String::from("\tunsubscribe: Stops the background subscription, if its active"));
 100      output.push(String::from("\tsnooze: Disables the background subscription messages printing"));
 101      output.push(String::from("\tunsnooze: Enables the background subscription messages printing"));
 102      output.push(String::from("\tscan: Scan the blockchain and parse relevant transactions"));
 103      output.push(String::from("\texplorer: Explorer related subcommands"));
 104      output.push(String::from("\talias: Token alias"));
 105      output.push(String::from("\ttoken: Token functionalities"));
 106      output.push(String::from("\tcontract: Contract functionalities"));
 107  }
 108  
 109  /// Auxiliary function to define the interactive shell completions.
 110  fn completion(buffer: &str, lc: &mut Vec<String>) {
 111      // Split commands so we always process the last one
 112      let commands: Vec<&str> = buffer.split('|').collect();
 113      // Grab the prefix
 114      let prefix = if commands.len() > 1 {
 115          commands[..commands.len() - 1].join("|") + "| "
 116      } else {
 117          String::from("")
 118      };
 119      let last = commands.last().unwrap().trim_start();
 120  
 121      // First we define the specific commands prefixes
 122      if last.starts_with("h") {
 123          lc.push(prefix + "help");
 124          return
 125      }
 126  
 127      if last.starts_with("k") {
 128          lc.push(prefix + "kaching");
 129          return
 130      }
 131  
 132      if last.starts_with("p") {
 133          lc.push(prefix + "ping");
 134          return
 135      }
 136  
 137      if last.starts_with("com") {
 138          lc.push(prefix + "completions");
 139          return
 140      }
 141  
 142      if last.starts_with("w") {
 143          lc.push(prefix.clone() + "wallet");
 144          lc.push(prefix.clone() + "wallet initialize");
 145          lc.push(prefix.clone() + "wallet keygen");
 146          lc.push(prefix.clone() + "wallet balance");
 147          lc.push(prefix.clone() + "wallet address");
 148          lc.push(prefix.clone() + "wallet addresses");
 149          lc.push(prefix.clone() + "wallet default-address");
 150          lc.push(prefix.clone() + "wallet secrets");
 151          lc.push(prefix.clone() + "wallet import-secrets");
 152          lc.push(prefix.clone() + "wallet tree");
 153          lc.push(prefix + "wallet coins");
 154          return
 155      }
 156  
 157      if last.starts_with("sp") {
 158          lc.push(prefix + "spend");
 159          return
 160      }
 161  
 162      if last.starts_with("unsp") {
 163          lc.push(prefix + "unspend");
 164          return
 165      }
 166  
 167      if last.starts_with("tr") {
 168          lc.push(prefix + "transfer");
 169          return
 170      }
 171  
 172      if last.starts_with("o") {
 173          lc.push(prefix.clone() + "otc");
 174          lc.push(prefix.clone() + "otc init");
 175          lc.push(prefix.clone() + "otc join");
 176          lc.push(prefix.clone() + "otc inspect");
 177          lc.push(prefix + "otc sign");
 178          return
 179      }
 180  
 181      if last.starts_with("d") {
 182          lc.push(prefix.clone() + "dao");
 183          lc.push(prefix.clone() + "dao create");
 184          lc.push(prefix.clone() + "dao view");
 185          lc.push(prefix.clone() + "dao import");
 186          lc.push(prefix.clone() + "dao list");
 187          lc.push(prefix.clone() + "dao balance");
 188          lc.push(prefix.clone() + "dao mint");
 189          lc.push(prefix.clone() + "dao propose-transfer");
 190          lc.push(prefix.clone() + "dao propose-generic");
 191          lc.push(prefix.clone() + "dao proposals");
 192          lc.push(prefix.clone() + "dao proposal");
 193          lc.push(prefix.clone() + "dao proposal-import");
 194          lc.push(prefix.clone() + "dao vote");
 195          lc.push(prefix.clone() + "dao exec");
 196          lc.push(prefix + "dao spend-hook");
 197          return
 198      }
 199  
 200      if last.starts_with("at") {
 201          lc.push(prefix + "attach-fee");
 202          return
 203      }
 204  
 205      if last.starts_with("i") {
 206          lc.push(prefix + "inspect");
 207          return
 208      }
 209  
 210      if last.starts_with("b") {
 211          lc.push(prefix + "broadcast");
 212          return
 213      }
 214  
 215      if last.starts_with("su") {
 216          lc.push(prefix + "subscribe");
 217          return
 218      }
 219  
 220      if last.starts_with("unsu") {
 221          lc.push(prefix + "unsubscribe");
 222          return
 223      }
 224  
 225      if last.starts_with("sn") {
 226          lc.push(prefix + "snooze");
 227          return
 228      }
 229  
 230      if last.starts_with("unsn") {
 231          lc.push(prefix + "unsnooze");
 232          return
 233      }
 234  
 235      if last.starts_with("sc") {
 236          lc.push(prefix.clone() + "scan");
 237          lc.push(prefix + "scan --reset");
 238          return
 239      }
 240  
 241      if last.starts_with("e") {
 242          lc.push(prefix.clone() + "explorer");
 243          lc.push(prefix.clone() + "explorer fetch-tx");
 244          lc.push(prefix.clone() + "explorer simulate-tx");
 245          lc.push(prefix.clone() + "explorer txs-history");
 246          lc.push(prefix.clone() + "explorer clear-reverted");
 247          lc.push(prefix + "explorer scanned-blocks");
 248          return
 249      }
 250  
 251      if last.starts_with("al") {
 252          lc.push(prefix.clone() + "alias");
 253          lc.push(prefix.clone() + "alias add");
 254          lc.push(prefix.clone() + "alias show");
 255          lc.push(prefix + "alias remove");
 256          return
 257      }
 258  
 259      if last.starts_with("to") {
 260          lc.push(prefix.clone() + "token");
 261          lc.push(prefix.clone() + "token import");
 262          lc.push(prefix.clone() + "token generate-mint");
 263          lc.push(prefix.clone() + "token list");
 264          lc.push(prefix.clone() + "token mint");
 265          lc.push(prefix + "token freeze");
 266          return
 267      }
 268  
 269      if last.starts_with("con") {
 270          lc.push(prefix.clone() + "contract");
 271          lc.push(prefix.clone() + "contract generate-deploy");
 272          lc.push(prefix.clone() + "contract list");
 273          lc.push(prefix.clone() + "contract export-data");
 274          lc.push(prefix.clone() + "contract deploy");
 275          lc.push(prefix + "contract lock");
 276          return
 277      }
 278  
 279      // Now the catch alls
 280      if last.starts_with("a") {
 281          lc.push(prefix.clone() + "attach-fee");
 282          lc.push(prefix.clone() + "alias");
 283          lc.push(prefix.clone() + "alias add");
 284          lc.push(prefix.clone() + "alias show");
 285          lc.push(prefix + "alias remove");
 286          return
 287      }
 288  
 289      if last.starts_with("c") {
 290          lc.push(prefix.clone() + "completions");
 291          lc.push(prefix.clone() + "contract");
 292          lc.push(prefix.clone() + "contract generate-deploy");
 293          lc.push(prefix.clone() + "contract list");
 294          lc.push(prefix.clone() + "contract export-data");
 295          lc.push(prefix.clone() + "contract deploy");
 296          lc.push(prefix + "contract lock");
 297          return
 298      }
 299  
 300      if last.starts_with("s") {
 301          lc.push(prefix.clone() + "spend");
 302          lc.push(prefix.clone() + "subscribe");
 303          lc.push(prefix.clone() + "snooze");
 304          lc.push(prefix.clone() + "scan");
 305          lc.push(prefix + "scan --reset");
 306          return
 307      }
 308  
 309      if last.starts_with("t") {
 310          lc.push(prefix.clone() + "transfer");
 311          lc.push(prefix.clone() + "token");
 312          lc.push(prefix.clone() + "token import");
 313          lc.push(prefix.clone() + "token generate-mint");
 314          lc.push(prefix.clone() + "token list");
 315          lc.push(prefix.clone() + "token mint");
 316          lc.push(prefix + "token freeze");
 317          return
 318      }
 319  
 320      if last.starts_with("u") {
 321          lc.push(prefix.clone() + "unspend");
 322          lc.push(prefix.clone() + "unsubscribe");
 323          lc.push(prefix + "unsnooze");
 324      }
 325  }
 326  
 327  /// Auxiliary function to define the interactive shell hints.
 328  fn hints(buffer: &str) -> Option<(String, i32, bool)> {
 329      // Split commands so we always process the last one
 330      let commands: Vec<&str> = buffer.split('|').collect();
 331      let last = commands.last().unwrap().trim_start();
 332      let color = 35; // magenta
 333      let bold = false;
 334      match last {
 335          "completions " => Some(("<shell>".to_string(), color, bold)),
 336          "wallet " => Some(("(initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)".to_string(), color, bold)),
 337          "wallet default-address " => Some(("<index>".to_string(), color, bold)),
 338          "unspend " => Some(("<coin>".to_string(), color, bold)),
 339          "transfer " => Some(("[--half-split] <amount> <token> <recipient> [spend_hook] [user_data]".to_string(), color, bold)),
 340          "otc " => Some(("(init|join|inspect|sign)".to_string(), color, bold)),
 341          "otc init " => Some(("<value_pair> <token_pair>".to_string(), color, bold)),
 342          "dao " => Some(("(create|view|import|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)".to_string(), color, bold)),
 343          "dao create " => Some(("<proposer-limit> <quorum> <early-exec-quorum> <approval-ratio> <gov-token-id>".to_string(), color, bold)),
 344          "dao import " => Some(("<name>".to_string(), color, bold)),
 345          "dao list " => Some(("[name]".to_string(), color, bold)),
 346          "dao balance " => Some(("<name>".to_string(), color, bold)),
 347          "dao mint " => Some(("<name>".to_string(), color, bold)),
 348          "dao propose-transfer " => Some(("<name> <duration> <amount> <token> <recipient> [spend-hook] [user-data]".to_string(), color, bold)),
 349          "dao propose-generic" => Some(("<name> <duration> [user-data]".to_string(), color, bold)),
 350          "dao proposals " => Some(("<name>".to_string(), color, bold)),
 351          "dao proposal " => Some(("[--(export|mint-proposal)] <bulla>".to_string(), color, bold)),
 352          "dao vote " => Some(("<bulla> <vote> [vote-weight]".to_string(), color, bold)),
 353          "dao exec " => Some(("[--early] <bulla>".to_string(), color, bold)),
 354          "scan " => Some(("[--reset]".to_string(), color, bold)),
 355          "scan --reset " => Some(("<height>".to_string(), color, bold)),
 356          "explorer " => Some(("(fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks)".to_string(), color, bold)),
 357          "explorer fetch-tx " => Some(("[--encode] <tx-hash>".to_string(), color, bold)),
 358          "explorer txs-history " => Some(("[--encode] [tx-hash]".to_string(), color, bold)),
 359          "explorer scanned-blocks " => Some(("[height]".to_string(), color, bold)),
 360          "alias " => Some(("(add|show|remove)".to_string(), color, bold)),
 361          "alias add " => Some(("<alias> <token>".to_string(), color, bold)),
 362          "alias show " => Some(("[-a, --alias <alias>] [-t, --token <token>]".to_string(), color, bold)),
 363          "alias remove " => Some(("<alias>".to_string(), color, bold)),
 364          "token " => Some(("(import|generate-mint|list|mint|freeze)".to_string(), color, bold)),
 365          "token import " => Some(("<secret-key> <token-blind>".to_string(), color, bold)),
 366          "token mint " => Some(("<token> <amount> <recipient> [spend-hook] [user-data]".to_string(), color, bold)),
 367          "token freeze " => Some(("<token>".to_string(), color, bold)),
 368          "contract " => Some(("(generate-deploy|list|export-data|deploy|lock)".to_string(), color, bold)),
 369          "contract list " => Some(("[contract-id]".to_string(), color, bold)),
 370          "contract export-data " => Some(("<tx-hash>".to_string(), color, bold)),
 371          "contract deploy " => Some(("<deploy-auth> <wasm-path> [deploy-ix]".to_string(), color, bold)),
 372          "contract lock " => Some(("<deploy-auth>".to_string(), color, bold)),
 373          _ => None,
 374      }
 375  }
 376  
 377  /// Auxiliary function to start provided Drk as an interactive shell.
 378  /// Only sane/linenoise terminals are suported.
 379  pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &ExecutorPtr) {
 380      // Expand the history file path
 381      let history_path = match expand_path(history_path) {
 382          Ok(p) => p,
 383          Err(e) => {
 384              eprintln!("Error while expanding history file path: {e}");
 385              return
 386          }
 387      };
 388      let history_path = history_path.into_os_string();
 389      let history_file = history_path.to_str().unwrap();
 390  
 391      // Set the completion callback. This will be called every time the
 392      // user uses the <tab> key.
 393      linenoise_set_completion_callback(completion);
 394  
 395      // Set the shell hints
 396      linenoise_set_hints_callback(hints);
 397  
 398      // Load history from file.The history file is just a plain text file
 399      // where entries are separated by newlines.
 400      let _ = linenoise_history_load(history_file);
 401  
 402      // Create two detached tasks to use for block subscription
 403      let mut subscription_active = false;
 404      let mut snooze_active = false;
 405      let subscription_tasks = [StoppableTask::new(), StoppableTask::new()];
 406  
 407      // Create an unbounded smol channel, so we can have a printing
 408      // queue the background task can submit messages to the shell.
 409      let (shell_sender, shell_receiver) = unbounded();
 410  
 411      // Start the interactive shell
 412      loop {
 413          // Wait for next line to process
 414          let line = listen_for_line(&snooze_active, &shell_receiver).await;
 415  
 416          // Grab input or end if Ctrl-D or Ctrl-C was pressed
 417          let Some(line) = line else { break };
 418  
 419          // Check if line is empty
 420          if line.is_empty() {
 421              continue
 422          }
 423  
 424          // Add line to history
 425          linenoise_history_add(&line);
 426  
 427          // Split commands
 428          let commands: Vec<&str> = line.split('|').collect();
 429  
 430          // Process each command
 431          let mut output = vec![];
 432          'commands_loop: for (command_index, command) in commands.iter().enumerate() {
 433              let mut input = output;
 434              output = vec![];
 435  
 436              // Check if we have to output to a file
 437              let (mut command, file, append) = if command.contains('>') {
 438                  // Check if we write or append to the file
 439                  let mut split = ">";
 440                  let mut append = false;
 441                  if command.contains(">>") {
 442                      split = ">>";
 443                      append = true;
 444                  }
 445  
 446                  // Parse command parts
 447                  let parts: Vec<&str> = command.split(split).collect();
 448                  if parts.len() == 1 || parts[0].contains('>') {
 449                      output.push(format!("Malformed command output file definition: {command}"));
 450                      continue
 451                  }
 452                  let file = String::from(parts[1..].join("").trim());
 453                  if file.is_empty() || file.contains('>') {
 454                      output.push(format!("Malformed command output file definition: {command}"));
 455                      continue
 456                  }
 457                  (parts[0], Some(file), append)
 458              } else {
 459                  (*command, None, false)
 460              };
 461  
 462              // Check if we have to use a file as input
 463              if command.contains('<') {
 464                  // Parse command parts
 465                  let parts: Vec<&str> = command.split('<').collect();
 466                  if parts.len() == 1 {
 467                      output.push(format!("Malformed command input file definition: {command}"));
 468                      continue
 469                  }
 470  
 471                  // Read the input file name
 472                  let file = String::from(parts[1..].join("").trim());
 473                  if file.is_empty() {
 474                      output.push(format!("Malformed command input file definition: {command}"));
 475                      continue
 476                  }
 477  
 478                  // Expand the input file path
 479                  let file_path = match expand_path(&file) {
 480                      Ok(p) => p,
 481                      Err(e) => {
 482                          output.push(format!("Error while expanding input file path: {e}"));
 483                          continue
 484                      }
 485                  };
 486  
 487                  // Read the file lines
 488                  let file = match File::open(file_path) {
 489                      Ok(f) => f,
 490                      Err(e) => {
 491                          output.push(format!("Error while openning input file: {e}"));
 492                          continue
 493                      }
 494                  };
 495                  input = vec![];
 496                  for (index, line) in BufReader::new(file).lines().enumerate() {
 497                      match line {
 498                          Ok(l) => input.push(l),
 499                          Err(e) => {
 500                              output
 501                                  .push(format!("Error while reading input file line {index}: {e}"));
 502                              continue 'commands_loop
 503                          }
 504                      }
 505                  }
 506                  command = parts[0];
 507              }
 508  
 509              // Parse command parts
 510              let parts: Vec<&str> = command.split_whitespace().collect();
 511              if parts.is_empty() {
 512                  continue
 513              }
 514  
 515              // Handle command
 516              match parts[0] {
 517                  "help" => help(&mut output),
 518                  "kaching" => kaching().await,
 519                  "ping" => handle_ping(drk, &mut output).await,
 520                  "completions" => handle_completions(&parts, &mut output),
 521                  "wallet" => handle_wallet(drk, &parts, &input, &mut output).await,
 522                  "spend" => handle_spend(drk, &input, &mut output).await,
 523                  "unspend" => handle_unspend(drk, &parts, &mut output).await,
 524                  "transfer" => handle_transfer(drk, &parts, &mut output).await,
 525                  "otc" => handle_otc(drk, &parts, &input, &mut output).await,
 526                  "dao" => handle_dao(drk, &parts, &input, &mut output).await,
 527                  "attach-fee" => handle_attach_fee(drk, &input, &mut output).await,
 528                  "inspect" => handle_inspect(&input, &mut output).await,
 529                  "broadcast" => handle_broadcast(drk, &input, &mut output).await,
 530                  "subscribe" => {
 531                      handle_subscribe(
 532                          drk,
 533                          endpoint,
 534                          &mut subscription_active,
 535                          &subscription_tasks,
 536                          &shell_sender,
 537                          ex,
 538                      )
 539                      .await
 540                  }
 541                  "unsubscribe" => {
 542                      handle_unsubscribe(&mut subscription_active, &subscription_tasks).await
 543                  }
 544                  "snooze" => snooze_active = true,
 545                  "unsnooze" => snooze_active = false,
 546                  "scan" => {
 547                      handle_scan(
 548                          drk,
 549                          &subscription_active,
 550                          &parts,
 551                          &mut output,
 552                          &(command_index + 1 == commands.len() && file.is_none()),
 553                      )
 554                      .await
 555                  }
 556                  "explorer" => handle_explorer(drk, &parts, &input, &mut output).await,
 557                  "alias" => handle_alias(drk, &parts, &mut output).await,
 558                  "token" => handle_token(drk, &parts, &mut output).await,
 559                  "contract" => handle_contract(drk, &parts, &mut output).await,
 560                  _ => output.push(format!("Unrecognized command: {}", parts[0])),
 561              }
 562  
 563              // Write output to file, if requested
 564              if let Some(file) = file {
 565                  // Expand the output file path
 566                  let file_path = match expand_path(&file) {
 567                      Ok(p) => p,
 568                      Err(e) => {
 569                          output.push(format!("Error while expanding output file path: {e}"));
 570                          continue
 571                      }
 572                  };
 573  
 574                  // Open the file
 575                  let file = if append {
 576                      OpenOptions::new().create(true).append(true).open(&file_path)
 577                  } else {
 578                      File::create(file_path)
 579                  };
 580                  let mut file = match file {
 581                      Ok(f) => f,
 582                      Err(e) => {
 583                          output.push(format!("Error while openning output file: {e}"));
 584                          continue
 585                      }
 586                  };
 587  
 588                  // Seek end if we append to it
 589                  if append {
 590                      if let Err(e) = file.seek(SeekFrom::End(0)) {
 591                          output.push(format!("Error while seeking end of output file: {e}"));
 592                          continue
 593                      }
 594                  }
 595  
 596                  // Write the output
 597                  if let Err(e) = file.write_all((output.join("\n") + "\n").as_bytes()) {
 598                      output.push(format!("Error while writing output file: {e}"));
 599                      continue
 600                  }
 601                  output = vec![];
 602              }
 603          }
 604  
 605          // Handle last command output
 606          print_output(&output);
 607      }
 608  
 609      // Stop the subscription tasks if they are active
 610      subscription_tasks[0].stop_nowait();
 611      subscription_tasks[1].stop_nowait();
 612  
 613      // Write history file
 614      let _ = linenoise_history_save(history_file);
 615  }
 616  
 617  /// Auxiliary function to listen for linenoise input line and handle
 618  /// background task messages.
 619  async fn listen_for_line(
 620      snooze_active: &bool,
 621      shell_receiver: &Receiver<Vec<String>>,
 622  ) -> Option<String> {
 623      // Generate the linoise state structure
 624      let mut state = match LinenoiseState::edit_start(-1, -1, "drk> ") {
 625          Ok(s) => s,
 626          Err(e) => {
 627              eprintln!("Error while generating linenoise state: {e}");
 628              return None
 629          }
 630      };
 631  
 632      // Set stdin to non-blocking mode
 633      let fd = state.get_fd();
 634      unsafe {
 635          let flags = fcntl(fd, F_GETFL, 0);
 636          fcntl(fd, F_SETFL, flags | O_NONBLOCK);
 637      }
 638  
 639      // Read until we get a line to process
 640      let mut line = None;
 641      loop {
 642          // Future that polls stdin for input
 643          let input_future = async {
 644              loop {
 645                  match state.edit_feed() {
 646                      Ok(Some(l)) => {
 647                          line = Some(l);
 648                          break
 649                      }
 650                      Ok(None) => break,
 651                      Err(e) if e.kind() == ErrorKind::Interrupted => break,
 652                      Err(e) if e.kind() == ErrorKind::WouldBlock => {
 653                          // No data available, yield and retry
 654                          msleep(10).await;
 655                          continue
 656                      }
 657                      Err(e) => {
 658                          eprintln!("Error while reading linenoise feed: {e}");
 659                          break
 660                      }
 661                  }
 662              }
 663          };
 664  
 665          // Future that polls the channel
 666          let channel_future = async {
 667              loop {
 668                  if !shell_receiver.is_empty() {
 669                      break
 670                  }
 671                  msleep(1000).await;
 672              }
 673          };
 674  
 675          // Manage the futures
 676          select! {
 677              // When input is ready we break out the loop
 678              _ = input_future.fuse() => break,
 679              // Manage filled channel
 680              _ = channel_future.fuse() => {
 681                  // We only print if snooze is inactive,
 682                  // but have to consume the message regardless,
 683                  // so the queue gets empty.
 684                  if *snooze_active {
 685                      while !shell_receiver.is_empty() {
 686                          if let Err(e) = shell_receiver.recv().await {
 687                              eprintln!("Error while reading shell receiver channel: {e}");
 688                              break
 689                          }
 690                      }
 691                  } else {
 692                      // Hide prompt
 693                      if let Err(e) = state.hide() {
 694                          eprintln!("Error while hiding linenoise state: {e}");
 695                          break
 696                      };
 697  
 698                      // Restore blocking mode
 699                      unsafe {
 700                          let flags = fcntl(fd, F_GETFL, 0);
 701                          fcntl(fd, F_SETFL, flags & !O_NONBLOCK);
 702                      }
 703  
 704                      // Print output
 705                      while !shell_receiver.is_empty() {
 706                          match shell_receiver.recv().await {
 707                              Ok(msg) => {
 708                                  for line in msg {
 709                                      println!("{}\r", line.replace("\n", "\n\r"));
 710                                  }
 711                              }
 712                              Err(e) => {
 713                                  eprintln!("Error while reading shell receiver channel: {e}");
 714                                  break
 715                              }
 716                          }
 717                      }
 718  
 719                      // Set stdin to non-blocking mode
 720                      unsafe {
 721                          let flags = fcntl(fd, F_GETFL, 0);
 722                          fcntl(fd, F_SETFL, flags | O_NONBLOCK);
 723                      }
 724  
 725                      // Show prompt again
 726                      if let Err(e) = state.show() {
 727                          eprintln!("Error while showing linenoise state: {e}");
 728                          break
 729                      };
 730                  }
 731              }
 732          }
 733      }
 734  
 735      // Restore blocking mode
 736      unsafe {
 737          let flags = fcntl(fd, F_GETFL, 0);
 738          fcntl(fd, F_SETFL, flags & !O_NONBLOCK);
 739      }
 740  
 741      if let Err(e) = state.edit_stop() {
 742          eprintln!("Error while stopping linenoise state: {e}");
 743      };
 744      line
 745  }
 746  
 747  /// Auxiliary function to define the ping command handling.
 748  async fn handle_ping(drk: &DrkPtr, output: &mut Vec<String>) {
 749      if let Err(e) = drk.read().await.ping(output).await {
 750          output.push(format!("Error while executing ping command: {e}"))
 751      }
 752  }
 753  
 754  /// Auxiliary function to define the completions command handling.
 755  fn handle_completions(parts: &[&str], output: &mut Vec<String>) {
 756      // Check correct command structure
 757      if parts.len() != 2 {
 758          output.push(String::from("Malformed `completions` command"));
 759          output.push(String::from("Usage: completions <shell>"));
 760          return
 761      }
 762  
 763      match generate_completions(parts[1]) {
 764          Ok(completions) => output.push(completions),
 765          Err(e) => output.push(format!("Error while executing completions command: {e}")),
 766      }
 767  }
 768  
 769  /// Auxiliary function to define the wallet command handling.
 770  async fn handle_wallet(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
 771      // Check correct command structure
 772      if parts.len() < 2 {
 773          output.push(String::from("Malformed `wallet` command"));
 774          output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)"));
 775          return
 776      }
 777  
 778      // Handle subcommand
 779      match parts[1] {
 780          "initialize" => handle_wallet_initialize(drk, output).await,
 781          "keygen" => handle_wallet_keygen(drk, output).await,
 782          "balance" => handle_wallet_balance(drk, output).await,
 783          "address" => handle_wallet_address(drk, output).await,
 784          "addresses" => handle_wallet_addresses(drk, output).await,
 785          "default-address" => handle_wallet_default_address(drk, parts, output).await,
 786          "secrets" => handle_wallet_secrets(drk, output).await,
 787          "import-secrets" => handle_wallet_import_secrets(drk, input, output).await,
 788          "tree" => handle_wallet_tree(drk, output).await,
 789          "coins" => handle_wallet_coins(drk, output).await,
 790          _ => {
 791              output.push(format!("Unrecognized wallet subcommand: {}", parts[1]));
 792              output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)"));
 793          }
 794      }
 795  }
 796  
 797  /// Auxiliary function to define the wallet initialize subcommand handling.
 798  async fn handle_wallet_initialize(drk: &DrkPtr, output: &mut Vec<String>) {
 799      let lock = drk.read().await;
 800      if let Err(e) = lock.initialize_wallet().await {
 801          output.push(format!("Error initializing wallet: {e}"));
 802          return
 803      }
 804      if let Err(e) = lock.initialize_money(output).await {
 805          output.push(format!("Failed to initialize Money: {e}"));
 806          return
 807      }
 808      if let Err(e) = lock.initialize_dao().await {
 809          output.push(format!("Failed to initialize DAO: {e}"));
 810          return
 811      }
 812      if let Err(e) = lock.initialize_deployooor() {
 813          output.push(format!("Failed to initialize Deployooor: {e}"));
 814      }
 815  }
 816  
 817  /// Auxiliary function to define the wallet keygen subcommand handling.
 818  async fn handle_wallet_keygen(drk: &DrkPtr, output: &mut Vec<String>) {
 819      if let Err(e) = drk.read().await.money_keygen(output).await {
 820          output.push(format!("Failed to generate keypair: {e}"));
 821      }
 822  }
 823  
 824  /// Auxiliary function to define the wallet balance subcommand handling.
 825  async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
 826      let lock = drk.read().await;
 827      let balmap = match lock.money_balance().await {
 828          Ok(m) => m,
 829          Err(e) => {
 830              output.push(format!("Failed to fetch balances map: {e}"));
 831              return
 832          }
 833      };
 834  
 835      let aliases_map = match lock.get_aliases_mapped_by_token().await {
 836          Ok(m) => m,
 837          Err(e) => {
 838              output.push(format!("Failed to fetch aliases map: {e}"));
 839              return
 840          }
 841      };
 842  
 843      // Create a prettytable with the new data:
 844      let mut table = Table::new();
 845      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
 846      table.set_titles(row!["Token ID", "Aliases", "Balance"]);
 847      for (token_id, balance) in balmap.iter() {
 848          let aliases = match aliases_map.get(token_id) {
 849              Some(a) => a,
 850              None => "-",
 851          };
 852  
 853          table.add_row(row![token_id, aliases, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]);
 854      }
 855  
 856      if table.is_empty() {
 857          output.push(String::from("No unspent balances found"));
 858      } else {
 859          output.push(format!("{table}"));
 860      }
 861  }
 862  
 863  /// Auxiliary function to define the wallet address subcommand handling.
 864  async fn handle_wallet_address(drk: &DrkPtr, output: &mut Vec<String>) {
 865      match drk.read().await.default_address().await {
 866          Ok(address) => output.push(format!("{address}")),
 867          Err(e) => output.push(format!("Failed to fetch default address: {e}")),
 868      }
 869  }
 870  
 871  /// Auxiliary function to define the wallet addresses subcommand handling.
 872  async fn handle_wallet_addresses(drk: &DrkPtr, output: &mut Vec<String>) {
 873      let addresses = match drk.read().await.addresses().await {
 874          Ok(a) => a,
 875          Err(e) => {
 876              output.push(format!("Failed to fetch addresses: {e}"));
 877              return
 878          }
 879      };
 880  
 881      // Create a prettytable with the new data:
 882      let mut table = Table::new();
 883      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
 884      table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
 885      for (key_id, public_key, secret_key, is_default) in addresses {
 886          let is_default = match is_default {
 887              1 => "*",
 888              _ => "",
 889          };
 890          table.add_row(row![key_id, public_key, secret_key, is_default]);
 891      }
 892  
 893      if table.is_empty() {
 894          output.push(String::from("No addresses found"));
 895      } else {
 896          output.push(format!("{table}"));
 897      }
 898  }
 899  
 900  /// Auxiliary function to define the wallet default address subcommand handling.
 901  async fn handle_wallet_default_address(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
 902      if parts.len() != 3 {
 903          output.push(String::from("Malformed `wallet default-address` subcommand"));
 904          output.push(String::from("Usage: wallet default-address <index>"));
 905          return
 906      }
 907  
 908      let index = match usize::from_str(parts[2]) {
 909          Ok(i) => i,
 910          Err(e) => {
 911              output.push(format!("Invalid address id: {e}"));
 912              return
 913          }
 914      };
 915  
 916      if let Err(e) = drk.read().await.set_default_address(index) {
 917          output.push(format!("Failed to set default address: {e}"));
 918      }
 919  }
 920  
 921  /// Auxiliary function to define the wallet secrets subcommand handling.
 922  async fn handle_wallet_secrets(drk: &DrkPtr, output: &mut Vec<String>) {
 923      match drk.read().await.get_money_secrets().await {
 924          Ok(secrets) => {
 925              for secret in secrets {
 926                  output.push(format!("{secret}"));
 927              }
 928          }
 929          Err(e) => output.push(format!("Failed to fetch secrets: {e}")),
 930      }
 931  }
 932  
 933  /// Auxiliary function to define the wallet import secrets subcommand handling.
 934  async fn handle_wallet_import_secrets(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
 935      let mut secrets = vec![];
 936      // Parse input or read from stdin
 937      if input.is_empty() {
 938          for (i, line) in stdin().lines().enumerate() {
 939              let Ok(line) = line else { continue };
 940  
 941              let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
 942                  output.push(format!("Warning: Failed to decode secret on line {i}"));
 943                  continue
 944              };
 945              let Ok(secret) = deserialize_async(&bytes).await else {
 946                  output.push(format!("Warning: Failed to deserialize secret on line {i}"));
 947                  continue
 948              };
 949              secrets.push(secret);
 950          }
 951      } else {
 952          for (i, line) in input.iter().enumerate() {
 953              let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
 954                  output.push(format!("Warning: Failed to decode secret on line {i}"));
 955                  continue
 956              };
 957              let Ok(secret) = deserialize_async(&bytes).await else {
 958                  output.push(format!("Warning: Failed to deserialize secret on line {i}"));
 959                  continue
 960              };
 961              secrets.push(secret);
 962          }
 963      }
 964  
 965      match drk.read().await.import_money_secrets(secrets, output).await {
 966          Ok(pubkeys) => {
 967              for key in pubkeys {
 968                  output.push(format!("{key}"));
 969              }
 970          }
 971          Err(e) => output.push(format!("Failed to import secrets: {e}")),
 972      }
 973  }
 974  
 975  /// Auxiliary function to define the wallet tree subcommand handling.
 976  async fn handle_wallet_tree(drk: &DrkPtr, output: &mut Vec<String>) {
 977      match drk.read().await.get_money_tree().await {
 978          Ok(tree) => output.push(format!("{tree:#?}")),
 979          Err(e) => output.push(format!("Failed to fetch tree: {e}")),
 980      }
 981  }
 982  
 983  /// Auxiliary function to define the wallet coins subcommand handling.
 984  async fn handle_wallet_coins(drk: &DrkPtr, output: &mut Vec<String>) {
 985      let lock = drk.read().await;
 986      let coins = match lock.get_coins(true).await {
 987          Ok(c) => c,
 988          Err(e) => {
 989              output.push(format!("Failed to fetch coins: {e}"));
 990              return
 991          }
 992      };
 993  
 994      if coins.is_empty() {
 995          return
 996      }
 997  
 998      let aliases_map = match lock.get_aliases_mapped_by_token().await {
 999          Ok(m) => m,
1000          Err(e) => {
1001              output.push(format!("Failed to fetch aliases map: {e}"));
1002              return
1003          }
1004      };
1005  
1006      let mut table = Table::new();
1007      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
1008      table.set_titles(row![
1009          "Coin",
1010          "Token ID",
1011          "Aliases",
1012          "Value",
1013          "Spend Hook",
1014          "User Data",
1015          "Creation Height",
1016          "Spent",
1017          "Spent Height",
1018          "Spent TX"
1019      ]);
1020      for coin in coins {
1021          let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
1022              Some(a) => a,
1023              None => "-",
1024          };
1025  
1026          let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
1027              format!("{}", coin.0.note.spend_hook)
1028          } else {
1029              String::from("-")
1030          };
1031  
1032          let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
1033              bs58::encode(&serialize_async(&coin.0.note.user_data).await).into_string().to_string()
1034          } else {
1035              String::from("-")
1036          };
1037  
1038          let spent_height = match coin.3 {
1039              Some(spent_height) => spent_height.to_string(),
1040              None => String::from("-"),
1041          };
1042  
1043          table.add_row(row![
1044              bs58::encode(&serialize_async(&coin.0.coin.inner()).await).into_string().to_string(),
1045              coin.0.note.token_id,
1046              aliases,
1047              format!(
1048                  "{} ({})",
1049                  coin.0.note.value,
1050                  encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
1051              ),
1052              spend_hook,
1053              user_data,
1054              coin.1,
1055              coin.2,
1056              spent_height,
1057              coin.4,
1058          ]);
1059      }
1060  
1061      output.push(format!("{table}"));
1062  }
1063  
1064  /// Auxiliary function to define the spend command handling.
1065  async fn handle_spend(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
1066      let tx = match parse_tx_from_input(input).await {
1067          Ok(t) => t,
1068          Err(e) => {
1069              output.push(format!("Error while parsing transaction: {e}"));
1070              return
1071          }
1072      };
1073  
1074      if let Err(e) = drk.read().await.mark_tx_spend(&tx, output).await {
1075          output.push(format!("Failed to mark transaction coins as spent: {e}"))
1076      }
1077  }
1078  
1079  /// Auxiliary function to define the unspend command handling.
1080  async fn handle_unspend(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1081      // Check correct command structure
1082      if parts.len() != 2 {
1083          output.push(String::from("Malformed `unspend` command"));
1084          output.push(String::from("Usage: unspend <coin>"));
1085          return
1086      }
1087  
1088      let bytes = match bs58::decode(&parts[1]).into_vec() {
1089          Ok(b) => b,
1090          Err(e) => {
1091              output.push(format!("Invalid coin: {e}"));
1092              return
1093          }
1094      };
1095  
1096      let bytes: [u8; 32] = match bytes.try_into() {
1097          Ok(b) => b,
1098          Err(e) => {
1099              output.push(format!("Invalid coin: {e:?}"));
1100              return
1101          }
1102      };
1103  
1104      let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1105          Some(v) => v,
1106          None => {
1107              output.push(String::from("Invalid coin"));
1108              return
1109          }
1110      };
1111  
1112      if let Err(e) = drk.read().await.unspend_coin(&Coin::from(elem)).await {
1113          output.push(format!("Failed to mark coin as unspent: {e}"))
1114      }
1115  }
1116  
1117  /// Auxiliary function to define the transfer command handling.
1118  async fn handle_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1119      // Check correct command structure
1120      if parts.len() < 4 || parts.len() > 7 {
1121          output.push(String::from("Malformed `transfer` command"));
1122          output.push(String::from(
1123              "Usage: transfer [--half-split] <amount> <token> <recipient> [spend_hook] [user_data]",
1124          ));
1125          return
1126      }
1127  
1128      // Parse command
1129      let mut index = 1;
1130      let mut half_split = false;
1131      if parts[index] == "--half-split" {
1132          half_split = true;
1133          index += 1;
1134      }
1135  
1136      let amount = String::from(parts[index]);
1137      if let Err(e) = f64::from_str(&amount) {
1138          output.push(format!("Invalid amount: {e}"));
1139          return
1140      }
1141      index += 1;
1142  
1143      let lock = drk.read().await;
1144      let token_id = match lock.get_token(String::from(parts[index])).await {
1145          Ok(t) => t,
1146          Err(e) => {
1147              output.push(format!("Invalid token ID: {e}"));
1148              return
1149          }
1150      };
1151      index += 1;
1152  
1153      let rcpt = match PublicKey::from_str(parts[index]) {
1154          Ok(r) => r,
1155          Err(e) => {
1156              output.push(format!("Invalid recipient: {e}"));
1157              return
1158          }
1159      };
1160      index += 1;
1161  
1162      let spend_hook = if index < parts.len() {
1163          match FuncId::from_str(parts[index]) {
1164              Ok(s) => Some(s),
1165              Err(e) => {
1166                  output.push(format!("Invalid spend hook: {e}"));
1167                  return
1168              }
1169          }
1170      } else {
1171          None
1172      };
1173      index += 1;
1174  
1175      let user_data = if index < parts.len() {
1176          let bytes = match bs58::decode(&parts[index]).into_vec() {
1177              Ok(b) => b,
1178              Err(e) => {
1179                  output.push(format!("Invalid user data: {e}"));
1180                  return
1181              }
1182          };
1183  
1184          let bytes: [u8; 32] = match bytes.try_into() {
1185              Ok(b) => b,
1186              Err(e) => {
1187                  output.push(format!("Invalid user data: {e:?}"));
1188                  return
1189              }
1190          };
1191  
1192          let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1193              Some(v) => v,
1194              None => {
1195                  output.push(String::from("Invalid user data"));
1196                  return
1197              }
1198          };
1199  
1200          Some(elem)
1201      } else {
1202          None
1203      };
1204  
1205      match lock.transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split).await {
1206          Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
1207          Err(e) => output.push(format!("Failed to create payment transaction: {e}")),
1208      }
1209  }
1210  
1211  /// Auxiliary function to define the otc command handling.
1212  async fn handle_otc(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1213      // Check correct command structure
1214      if parts.len() < 2 {
1215          output.push(String::from("Malformed `otc` command"));
1216          output.push(String::from("Usage: otc (init|join|inspect|sign)"));
1217          return
1218      }
1219  
1220      // Handle subcommand
1221      match parts[1] {
1222          "init" => handle_otc_init(drk, parts, output).await,
1223          "join" => handle_otc_join(drk, parts, input, output).await,
1224          "inspect" => handle_otc_inspect(drk, parts, input, output).await,
1225          "sign" => handle_otc_sign(drk, parts, input, output).await,
1226          _ => {
1227              output.push(format!("Unrecognized OTC subcommand: {}", parts[1]));
1228              output.push(String::from("Usage: otc (init|join|inspect|sign)"));
1229          }
1230      }
1231  }
1232  
1233  /// Auxiliary function to define the otc init subcommand handling.
1234  async fn handle_otc_init(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1235      // Check correct subcommand structure
1236      if parts.len() != 4 {
1237          output.push(String::from("Malformed `otc init` subcommand"));
1238          output.push(String::from("Usage: otc init <value_pair> <token_pair>"));
1239          return
1240      }
1241  
1242      let value_pair = match parse_value_pair(parts[2]) {
1243          Ok(v) => v,
1244          Err(e) => {
1245              output.push(format!("Invalid value pair: {e}"));
1246              return
1247          }
1248      };
1249  
1250      let lock = drk.read().await;
1251      let token_pair = match parse_token_pair(&lock, parts[3]).await {
1252          Ok(t) => t,
1253          Err(e) => {
1254              output.push(format!("Invalid token pair: {e}"));
1255              return
1256          }
1257      };
1258  
1259      match lock.init_swap(value_pair, token_pair, None, None, None).await {
1260          Ok(half) => output.push(base64::encode(&serialize_async(&half).await)),
1261          Err(e) => output.push(format!("Failed to create swap transaction half: {e}")),
1262      }
1263  }
1264  
1265  /// Auxiliary function to define the otc join subcommand handling.
1266  async fn handle_otc_join(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1267      // Check correct subcommand structure
1268      if parts.len() != 2 {
1269          output.push(String::from("Malformed `otc join` subcommand"));
1270          output.push(String::from("Usage: otc join"));
1271          return
1272      }
1273  
1274      // Parse line from input or fallback to stdin if its empty
1275      let buf = match input.len() {
1276          0 => {
1277              let mut buf = String::new();
1278              if let Err(e) = stdin().read_to_string(&mut buf) {
1279                  output.push(format!("Failed to read from stdin: {e}"));
1280                  return
1281              };
1282              buf
1283          }
1284          1 => input[0].clone(),
1285          _ => {
1286              output.push(String::from("Multiline input provided"));
1287              return
1288          }
1289      };
1290  
1291      let Some(bytes) = base64::decode(buf.trim()) else {
1292          output.push(String::from("Failed to decode partial swap data"));
1293          return
1294      };
1295  
1296      let partial: PartialSwapData = match deserialize_async(&bytes).await {
1297          Ok(p) => p,
1298          Err(e) => {
1299              output.push(format!("Failed to deserialize partial swap data: {e}"));
1300              return
1301          }
1302      };
1303  
1304      match drk.read().await.join_swap(partial, None, None, None).await {
1305          Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1306          Err(e) => output.push(format!("Failed to create a join swap transaction: {e}")),
1307      }
1308  }
1309  
1310  /// Auxiliary function to define the otc inspect subcommand handling.
1311  async fn handle_otc_inspect(
1312      drk: &DrkPtr,
1313      parts: &[&str],
1314      input: &[String],
1315      output: &mut Vec<String>,
1316  ) {
1317      // Check correct subcommand structure
1318      if parts.len() != 2 {
1319          output.push(String::from("Malformed `otc inspect` subcommand"));
1320          output.push(String::from("Usage: otc inspect"));
1321          return
1322      }
1323  
1324      // Parse line from input or fallback to stdin if its empty
1325      let buf = match input.len() {
1326          0 => {
1327              let mut buf = String::new();
1328              if let Err(e) = stdin().read_to_string(&mut buf) {
1329                  output.push(format!("Failed to read from stdin: {e}"));
1330                  return
1331              };
1332              buf
1333          }
1334          1 => input[0].clone(),
1335          _ => {
1336              output.push(String::from("Multiline input provided"));
1337              return
1338          }
1339      };
1340  
1341      let Some(bytes) = base64::decode(buf.trim()) else {
1342          output.push(String::from("Failed to decode swap transaction"));
1343          return
1344      };
1345  
1346      if let Err(e) = drk.read().await.inspect_swap(bytes, output).await {
1347          output.push(format!("Failed to inspect swap: {e}"));
1348      }
1349  }
1350  
1351  /// Auxiliary function to define the otc sign subcommand handling.
1352  async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1353      // Check correct subcommand structure
1354      if parts.len() != 2 {
1355          output.push(String::from("Malformed `otc sign` subcommand"));
1356          output.push(String::from("Usage: otc sign"));
1357          return
1358      }
1359  
1360      let mut tx = match parse_tx_from_input(input).await {
1361          Ok(t) => t,
1362          Err(e) => {
1363              output.push(format!("Error while parsing transaction: {e}"));
1364              return
1365          }
1366      };
1367  
1368      match drk.read().await.sign_swap(&mut tx).await {
1369          Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
1370          Err(e) => output.push(format!("Failed to sign joined swap transaction: {e}")),
1371      }
1372  }
1373  
1374  /// Auxiliary function to define the dao command handling.
1375  async fn handle_dao(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1376      // Check correct command structure
1377      if parts.len() < 2 {
1378          output.push(String::from("Malformed `dao` command"));
1379          output.push(String::from("Usage: dao (create|view|import|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)"));
1380          return
1381      }
1382  
1383      // Handle subcommand
1384      match parts[1] {
1385          "create" => handle_dao_create(drk, parts, output).await,
1386          "view" => handle_dao_view(parts, input, output).await,
1387          "import" => handle_dao_import(drk, parts, input, output).await,
1388          "list" => handle_dao_list(drk, parts, output).await,
1389          "balance" => handle_dao_balance(drk, parts, output).await,
1390          "mint" => handle_dao_mint(drk, parts, output).await,
1391          "propose-transfer" => handle_dao_propose_transfer(drk, parts, output).await,
1392          "propose-generic" => handle_dao_propose_generic(drk, parts, output).await,
1393          "proposals" => handle_dao_proposals(drk, parts, output).await,
1394          "proposal" => handle_dao_proposal(drk, parts, output).await,
1395          "proposal-import" => handle_dao_proposal_import(drk, parts, input, output).await,
1396          "vote" => handle_dao_vote(drk, parts, output).await,
1397          "exec" => handle_dao_exec(drk, parts, output).await,
1398          "spend-hook" => handle_dao_spend_hook(parts, output).await,
1399          _ => {
1400              output.push(format!("Unrecognized DAO subcommand: {}", parts[1]));
1401              output.push(String::from("Usage: dao (create|view|import|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook)"));
1402          }
1403      }
1404  }
1405  
1406  /// Auxiliary function to define the dao create subcommand handling.
1407  async fn handle_dao_create(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1408      // Check correct subcommand structure
1409      if parts.len() != 7 {
1410          output.push(String::from("Malformed `dao create` subcommand"));
1411          output.push(String::from("Usage: dao create <proposer-limit> <quorum> <early-exec-quorum> <approval-ratio> <gov-token-id>"));
1412          return
1413      }
1414  
1415      if let Err(e) = f64::from_str(parts[2]) {
1416          output.push(format!("Invalid proposer limit: {e}"));
1417          return
1418      }
1419      let proposer_limit = match decode_base10(parts[2], BALANCE_BASE10_DECIMALS, true) {
1420          Ok(p) => p,
1421          Err(e) => {
1422              output.push(format!("Error while parsing proposer limit: {e}"));
1423              return
1424          }
1425      };
1426  
1427      if let Err(e) = f64::from_str(parts[3]) {
1428          output.push(format!("Invalid quorum: {e}"));
1429          return
1430      }
1431      let quorum = match decode_base10(parts[3], BALANCE_BASE10_DECIMALS, true) {
1432          Ok(q) => q,
1433          Err(e) => {
1434              output.push(format!("Error while parsing quorum: {e}"));
1435              return
1436          }
1437      };
1438  
1439      if let Err(e) = f64::from_str(parts[4]) {
1440          output.push(format!("Invalid early exec quorum: {e}"));
1441          return
1442      }
1443      let early_exec_quorum = match decode_base10(parts[4], BALANCE_BASE10_DECIMALS, true) {
1444          Ok(e) => e,
1445          Err(e) => {
1446              output.push(format!("Error while parsing early exec quorum: {e}"));
1447              return
1448          }
1449      };
1450  
1451      let approval_ratio = match f64::from_str(parts[5]) {
1452          Ok(a) => {
1453              if a > 1.0 {
1454                  output.push(String::from("Error: Approval ratio cannot be >1.0"));
1455                  return
1456              }
1457              a
1458          }
1459          Err(e) => {
1460              output.push(format!("Invalid approval ratio: {e}"));
1461              return
1462          }
1463      };
1464      let approval_ratio_base = 100_u64;
1465      let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
1466  
1467      let gov_token_id = match drk.read().await.get_token(String::from(parts[6])).await {
1468          Ok(g) => g,
1469          Err(e) => {
1470              output.push(format!("Invalid Token ID: {e}"));
1471              return
1472          }
1473      };
1474  
1475      let notes_keypair = Keypair::random(&mut OsRng);
1476      let proposer_keypair = Keypair::random(&mut OsRng);
1477      let proposals_keypair = Keypair::random(&mut OsRng);
1478      let votes_keypair = Keypair::random(&mut OsRng);
1479      let exec_keypair = Keypair::random(&mut OsRng);
1480      let early_exec_keypair = Keypair::random(&mut OsRng);
1481      let bulla_blind = BaseBlind::random(&mut OsRng);
1482  
1483      let params = DaoParams::new(
1484          proposer_limit,
1485          quorum,
1486          early_exec_quorum,
1487          approval_ratio_base,
1488          approval_ratio_quot,
1489          gov_token_id,
1490          Some(notes_keypair.secret),
1491          notes_keypair.public,
1492          Some(proposer_keypair.secret),
1493          proposer_keypair.public,
1494          Some(proposals_keypair.secret),
1495          proposals_keypair.public,
1496          Some(votes_keypair.secret),
1497          votes_keypair.public,
1498          Some(exec_keypair.secret),
1499          exec_keypair.public,
1500          Some(early_exec_keypair.secret),
1501          early_exec_keypair.public,
1502          bulla_blind,
1503      );
1504  
1505      output.push(params.toml_str());
1506  }
1507  
1508  /// Auxiliary function to define the dao view subcommand handling.
1509  async fn handle_dao_view(parts: &[&str], input: &[String], output: &mut Vec<String>) {
1510      // Check correct subcommand structure
1511      if parts.len() != 2 {
1512          output.push(String::from("Malformed `dao view` subcommand"));
1513          output.push(String::from("Usage: dao view"));
1514          return
1515      }
1516  
1517      // Parse lines from input or fallback to stdin if its empty
1518      let buf = match input.len() {
1519          0 => {
1520              let mut buf = String::new();
1521              if let Err(e) = stdin().read_to_string(&mut buf) {
1522                  output.push(format!("Failed to read from stdin: {e}"));
1523                  return
1524              };
1525              buf
1526          }
1527          _ => input.join("\n"),
1528      };
1529  
1530      let params = match DaoParams::from_toml_str(&buf) {
1531          Ok(p) => p,
1532          Err(e) => {
1533              output.push(format!("Error while parsing DAO params: {e}"));
1534              return
1535          }
1536      };
1537  
1538      output.push(format!("{params}"));
1539  }
1540  
1541  /// Auxiliary function to define the dao import subcommand handling.
1542  async fn handle_dao_import(
1543      drk: &DrkPtr,
1544      parts: &[&str],
1545      input: &[String],
1546      output: &mut Vec<String>,
1547  ) {
1548      // Check correct subcommand structure
1549      if parts.len() != 3 {
1550          output.push(String::from("Malformed `dao import` subcommand"));
1551          output.push(String::from("Usage: dao import <name>"));
1552          return
1553      }
1554  
1555      // Parse lines from input or fallback to stdin if its empty
1556      let buf = match input.len() {
1557          0 => {
1558              let mut buf = String::new();
1559              if let Err(e) = stdin().read_to_string(&mut buf) {
1560                  output.push(format!("Failed to read from stdin: {e}"));
1561                  return
1562              };
1563              buf
1564          }
1565          _ => input.join("\n"),
1566      };
1567  
1568      let params = match DaoParams::from_toml_str(&buf) {
1569          Ok(p) => p,
1570          Err(e) => {
1571              output.push(format!("Error while parsing DAO params: {e}"));
1572              return
1573          }
1574      };
1575  
1576      if let Err(e) = drk.read().await.import_dao(parts[2], &params, output).await {
1577          output.push(format!("Failed to import DAO: {e}"))
1578      }
1579  }
1580  
1581  /// Auxiliary function to define the dao list subcommand handling.
1582  async fn handle_dao_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1583      // Check correct subcommand structure
1584      if parts.len() != 2 && parts.len() != 3 {
1585          output.push(String::from("Malformed `dao list` subcommand"));
1586          output.push(String::from("Usage: dao list [name]"));
1587          return
1588      }
1589  
1590      let name = if parts.len() == 3 { Some(String::from(parts[2])) } else { None };
1591  
1592      if let Err(e) = drk.read().await.dao_list(&name, output).await {
1593          output.push(format!("Failed to list DAO: {e}"))
1594      }
1595  }
1596  
1597  /// Auxiliary function to define the dao balance subcommand handling.
1598  async fn handle_dao_balance(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1599      // Check correct subcommand structure
1600      if parts.len() != 3 {
1601          output.push(String::from("Malformed `dao balance` subcommand"));
1602          output.push(String::from("Usage: dao balance <name>"));
1603          return
1604      }
1605  
1606      let lock = drk.read().await;
1607      let balmap = match lock.dao_balance(parts[2]).await {
1608          Ok(b) => b,
1609          Err(e) => {
1610              output.push(format!("Failed to fetch DAO balance: {e}"));
1611              return
1612          }
1613      };
1614  
1615      let aliases_map = match lock.get_aliases_mapped_by_token().await {
1616          Ok(m) => m,
1617          Err(e) => {
1618              output.push(format!("Failed to fetch aliases map: {e}"));
1619              return
1620          }
1621      };
1622  
1623      let mut table = Table::new();
1624      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
1625      table.set_titles(row!["Token ID", "Aliases", "Balance"]);
1626      for (token_id, balance) in balmap.iter() {
1627          let aliases = match aliases_map.get(token_id) {
1628              Some(a) => a,
1629              None => "-",
1630          };
1631  
1632          table.add_row(row![token_id, aliases, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]);
1633      }
1634  
1635      if table.is_empty() {
1636          output.push(String::from("No unspent balances found"))
1637      } else {
1638          output.push(format!("{table}"));
1639      }
1640  }
1641  
1642  /// Auxiliary function to define the dao mint subcommand handling.
1643  async fn handle_dao_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1644      // Check correct subcommand structure
1645      if parts.len() != 3 {
1646          output.push(String::from("Malformed `dao mint` subcommand"));
1647          output.push(String::from("Usage: dao mint <name>"));
1648          return
1649      }
1650  
1651      match drk.read().await.dao_mint(parts[2]).await {
1652          Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1653          Err(e) => output.push(format!("Failed to mint DAO: {e}")),
1654      }
1655  }
1656  
1657  /// Auxiliary function to define the dao propose transfer subcommand handling.
1658  async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1659      // Check correct subcommand structure
1660      if parts.len() < 7 || parts.len() > 9 {
1661          output.push(String::from("Malformed `dao proposal-transfer` subcommand"));
1662          output.push(String::from("Usage: dao proposal-transfer <name> <duration> <amount> <token> <recipient> [spend-hook] [user-data]"));
1663          return
1664      }
1665  
1666      let duration = match u64::from_str(parts[3]) {
1667          Ok(d) => d,
1668          Err(e) => {
1669              output.push(format!("Invalid duration: {e}"));
1670              return
1671          }
1672      };
1673  
1674      let amount = String::from(parts[4]);
1675      if let Err(e) = f64::from_str(&amount) {
1676          output.push(format!("Invalid amount: {e}"));
1677          return
1678      }
1679  
1680      let lock = drk.read().await;
1681      let token_id = match lock.get_token(String::from(parts[5])).await {
1682          Ok(t) => t,
1683          Err(e) => {
1684              output.push(format!("Invalid token ID: {e}"));
1685              return
1686          }
1687      };
1688  
1689      let rcpt = match PublicKey::from_str(parts[6]) {
1690          Ok(r) => r,
1691          Err(e) => {
1692              output.push(format!("Invalid recipient: {e}"));
1693              return
1694          }
1695      };
1696  
1697      let mut index = 7;
1698      let spend_hook = if index < parts.len() {
1699          match FuncId::from_str(parts[index]) {
1700              Ok(s) => Some(s),
1701              Err(e) => {
1702                  output.push(format!("Invalid spend hook: {e}"));
1703                  return
1704              }
1705          }
1706      } else {
1707          None
1708      };
1709      index += 1;
1710  
1711      let user_data = if index < parts.len() {
1712          let bytes = match bs58::decode(&parts[index]).into_vec() {
1713              Ok(b) => b,
1714              Err(e) => {
1715                  output.push(format!("Invalid user data: {e}"));
1716                  return
1717              }
1718          };
1719  
1720          let bytes: [u8; 32] = match bytes.try_into() {
1721              Ok(b) => b,
1722              Err(e) => {
1723                  output.push(format!("Invalid user data: {e:?}"));
1724                  return
1725              }
1726          };
1727  
1728          let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1729              Some(v) => v,
1730              None => {
1731                  output.push(String::from("Invalid user data"));
1732                  return
1733              }
1734          };
1735  
1736          Some(elem)
1737      } else {
1738          None
1739      };
1740  
1741      match drk
1742          .read()
1743          .await
1744          .dao_propose_transfer(parts[2], duration, &amount, token_id, rcpt, spend_hook, user_data)
1745          .await
1746      {
1747          Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
1748          Err(e) => output.push(format!("Failed to create DAO transfer proposal: {e}")),
1749      }
1750  }
1751  
1752  /// Auxiliary function to define the dao propose generic subcommand handling.
1753  async fn handle_dao_propose_generic(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1754      // Check correct subcommand structure
1755      if parts.len() != 4 && parts.len() != 5 {
1756          output.push(String::from("Malformed `dao proposal-generic` subcommand"));
1757          output.push(String::from("Usage: dao proposal-generic <name> <duration> [user-data]"));
1758          return
1759      }
1760  
1761      let duration = match u64::from_str(parts[3]) {
1762          Ok(d) => d,
1763          Err(e) => {
1764              output.push(format!("Invalid duration: {e}"));
1765              return
1766          }
1767      };
1768  
1769      let user_data = if parts.len() == 5 {
1770          let bytes = match bs58::decode(&parts[4]).into_vec() {
1771              Ok(b) => b,
1772              Err(e) => {
1773                  output.push(format!("Invalid user data: {e}"));
1774                  return
1775              }
1776          };
1777  
1778          let bytes: [u8; 32] = match bytes.try_into() {
1779              Ok(b) => b,
1780              Err(e) => {
1781                  output.push(format!("Invalid user data: {e:?}"));
1782                  return
1783              }
1784          };
1785  
1786          let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1787              Some(v) => v,
1788              None => {
1789                  output.push(String::from("Invalid user data"));
1790                  return
1791              }
1792          };
1793  
1794          Some(elem)
1795      } else {
1796          None
1797      };
1798  
1799      match drk.read().await.dao_propose_generic(parts[2], duration, user_data).await {
1800          Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
1801          Err(e) => output.push(format!("Failed to create DAO generic proposal: {e}")),
1802      }
1803  }
1804  
1805  /// Auxiliary function to define the dao proposals subcommand handling.
1806  async fn handle_dao_proposals(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1807      // Check correct subcommand structure
1808      if parts.len() != 3 {
1809          output.push(String::from("Malformed `dao proposals` subcommand"));
1810          output.push(String::from("Usage: dao proposals <name>"));
1811          return
1812      }
1813  
1814      match drk.read().await.get_dao_proposals(parts[2]).await {
1815          Ok(proposals) => {
1816              for (i, proposal) in proposals.iter().enumerate() {
1817                  output.push(format!("{i}. {}", proposal.bulla()));
1818              }
1819          }
1820          Err(e) => output.push(format!("Failed to retrieve DAO proposals: {e}")),
1821      }
1822  }
1823  
1824  /// Auxiliary function to define the dao proposal subcommand handling.
1825  async fn handle_dao_proposal(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1826      // Check correct subcommand structure
1827      if parts.len() != 3 && parts.len() != 4 {
1828          output.push(String::from("Malformed `dao proposal` subcommand"));
1829          output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] <bulla>"));
1830          return
1831      }
1832  
1833      let mut index = 2;
1834      let (export, mint_proposal) = if parts.len() == 4 {
1835          let flags = match parts[index] {
1836              "--export" => (true, false),
1837              "--mint-proposal" => (false, true),
1838              _ => {
1839                  output.push(String::from("Malformed `dao proposal` subcommand"));
1840                  output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] <bulla>"));
1841                  return
1842              }
1843          };
1844          index += 1;
1845          flags
1846      } else {
1847          (false, false)
1848      };
1849  
1850      let bulla = match DaoProposalBulla::from_str(parts[index]) {
1851          Ok(b) => b,
1852          Err(e) => {
1853              output.push(format!("Invalid proposal bulla: {e}"));
1854              return
1855          }
1856      };
1857  
1858      let lock = drk.read().await;
1859      let proposal = match lock.get_dao_proposal_by_bulla(&bulla).await {
1860          Ok(p) => p,
1861          Err(e) => {
1862              output.push(format!("Failed to fetch DAO proposal: {e}"));
1863              return
1864          }
1865      };
1866  
1867      if export {
1868          // Retrieve the DAO
1869          let dao = match lock.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
1870              Ok(d) => d,
1871              Err(e) => {
1872                  output.push(format!("Failed to fetch DAO: {e}"));
1873                  return
1874              }
1875          };
1876  
1877          // Encypt the proposal
1878          let enc_note =
1879              AeadEncryptedNote::encrypt(&proposal, &dao.params.dao.proposals_public_key, &mut OsRng)
1880                  .unwrap();
1881  
1882          // Export it to base64
1883          output.push(base64::encode(&serialize_async(&enc_note).await));
1884          return
1885      }
1886  
1887      if mint_proposal {
1888          // Identify proposal type by its auth calls
1889          for call in &proposal.proposal.auth_calls {
1890              // We only support transfer right now
1891              if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1892                  match lock.dao_transfer_proposal_tx(&proposal).await {
1893                      Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1894                      Err(e) => output.push(format!("Failed to create DAO transfer proposal: {e}")),
1895                  }
1896                  return
1897              }
1898          }
1899  
1900          // If proposal has no auth calls, we consider it a generic one
1901          if proposal.proposal.auth_calls.is_empty() {
1902              match lock.dao_generic_proposal_tx(&proposal).await {
1903                  Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1904                  Err(e) => output.push(format!("Failed to create DAO generic proposal: {e}")),
1905              }
1906              return
1907          }
1908  
1909          output.push(String::from("Unsuported DAO proposal"));
1910          return
1911      }
1912  
1913      output.push(format!("{proposal}"));
1914  
1915      let mut contract_calls = "\nInvoked contracts:\n".to_string();
1916      for call in proposal.proposal.auth_calls {
1917          contract_calls.push_str(&format!(
1918              "\tContract: {}\n\tFunction: {}\n\tData: ",
1919              call.contract_id, call.function_code
1920          ));
1921  
1922          if call.auth_data.is_empty() {
1923              contract_calls.push_str("-\n");
1924              continue;
1925          }
1926  
1927          if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1928              // We know that the plaintext data live in the data plaintext vec
1929              if proposal.data.is_none() {
1930                  contract_calls.push_str("-\n");
1931                  continue;
1932              }
1933              let coin: CoinAttributes = match deserialize_async(proposal.data.as_ref().unwrap())
1934                  .await
1935              {
1936                  Ok(c) => c,
1937                  Err(e) => {
1938                      output.push(format!("Failed to deserialize transfer proposal coin data: {e}"));
1939                      return
1940                  }
1941              };
1942              let spend_hook = if coin.spend_hook == FuncId::none() {
1943                  "-".to_string()
1944              } else {
1945                  format!("{}", coin.spend_hook)
1946              };
1947  
1948              let user_data = if coin.user_data == pallas::Base::ZERO {
1949                  "-".to_string()
1950              } else {
1951                  format!("{:?}", coin.user_data)
1952              };
1953  
1954              contract_calls.push_str(&format!(
1955                  "\n\t\t{}: {}\n\t\t{}: {} ({})\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\n",
1956                  "Recipient",
1957                  coin.public_key,
1958                  "Amount",
1959                  coin.value,
1960                  encode_base10(coin.value, BALANCE_BASE10_DECIMALS),
1961                  "Token",
1962                  coin.token_id,
1963                  "Spend hook",
1964                  spend_hook,
1965                  "User data",
1966                  user_data,
1967                  "Blind",
1968                  coin.blind
1969              ));
1970          }
1971      }
1972  
1973      output.push(contract_calls);
1974  
1975      let votes = match lock.get_dao_proposal_votes(&bulla).await {
1976          Ok(v) => v,
1977          Err(e) => {
1978              output.push(format!("Failed to fetch DAO proposal votes: {e}"));
1979              return
1980          }
1981      };
1982      let mut total_yes_vote_value = 0;
1983      let mut total_no_vote_value = 0;
1984      let mut total_all_vote_value = 0;
1985      let mut table = Table::new();
1986      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
1987      table.set_titles(row!["Transaction", "Tokens", "Vote"]);
1988      for vote in votes {
1989          let vote_option = if vote.vote_option {
1990              total_yes_vote_value += vote.all_vote_value;
1991              "Yes"
1992          } else {
1993              total_no_vote_value += vote.all_vote_value;
1994              "No"
1995          };
1996          total_all_vote_value += vote.all_vote_value;
1997  
1998          table.add_row(row![
1999              vote.tx_hash,
2000              encode_base10(vote.all_vote_value, BALANCE_BASE10_DECIMALS),
2001              vote_option
2002          ]);
2003      }
2004  
2005      let outcome = if table.is_empty() {
2006          output.push(String::from("Votes: No votes found"));
2007          "Unknown"
2008      } else {
2009          output.push(String::from("Votes:"));
2010          output.push(format!("{table}"));
2011          output.push(format!(
2012              "Total tokens votes: {}",
2013              encode_base10(total_all_vote_value, BALANCE_BASE10_DECIMALS)
2014          ));
2015          let approval_ratio = (total_yes_vote_value as f64 * 100.0) / total_all_vote_value as f64;
2016          output.push(format!(
2017              "Total tokens Yes votes: {} ({approval_ratio:.2}%)",
2018              encode_base10(total_yes_vote_value, BALANCE_BASE10_DECIMALS)
2019          ));
2020          output.push(format!(
2021              "Total tokens No votes: {} ({:.2}%)",
2022              encode_base10(total_no_vote_value, BALANCE_BASE10_DECIMALS),
2023              (total_no_vote_value as f64 * 100.0) / total_all_vote_value as f64
2024          ));
2025  
2026          let dao = match lock.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
2027              Ok(d) => d,
2028              Err(e) => {
2029                  output.push(format!("Failed to fetch DAO: {e}"));
2030                  return
2031              }
2032          };
2033          if total_all_vote_value >= dao.params.dao.quorum &&
2034              approval_ratio >=
2035                  (dao.params.dao.approval_ratio_quot / dao.params.dao.approval_ratio_base)
2036                      as f64
2037          {
2038              "Approved"
2039          } else {
2040              "Rejected"
2041          }
2042      };
2043  
2044      if let Some(exec_tx_hash) = proposal.exec_tx_hash {
2045          output.push(format!("Proposal was executed on transaction: {exec_tx_hash}"));
2046          return
2047      }
2048  
2049      // Retrieve next block height and current block time target,
2050      // to compute their window.
2051      let next_block_height = match lock.get_next_block_height().await {
2052          Ok(n) => n,
2053          Err(e) => {
2054              output.push(format!("Failed to fetch next block height: {e}"));
2055              return
2056          }
2057      };
2058      let block_target = match lock.get_block_target().await {
2059          Ok(b) => b,
2060          Err(e) => {
2061              output.push(format!("Failed to fetch block target: {e}"));
2062              return
2063          }
2064      };
2065      let current_window = blockwindow(next_block_height, block_target);
2066      let end_time = proposal.proposal.creation_blockwindow + proposal.proposal.duration_blockwindows;
2067      let (voting_status, proposal_status_message) = if current_window < end_time {
2068          ("Ongoing", format!("Current proposal outcome: {outcome}"))
2069      } else {
2070          ("Concluded", format!("Proposal outcome: {outcome}"))
2071      };
2072      output.push(format!("Voting status: {voting_status}"));
2073      output.push(proposal_status_message);
2074  }
2075  
2076  /// Auxiliary function to define the dao proposal import subcommand handling.
2077  async fn handle_dao_proposal_import(
2078      drk: &DrkPtr,
2079      parts: &[&str],
2080      input: &[String],
2081      output: &mut Vec<String>,
2082  ) {
2083      // Check correct subcommand structure
2084      if parts.len() != 2 {
2085          output.push(String::from("Malformed `dao proposal-import` subcommand"));
2086          output.push(String::from("Usage: dao proposal-import"));
2087          return
2088      }
2089  
2090      // Parse line from input or fallback to stdin if its empty
2091      let buf = match input.len() {
2092          0 => {
2093              let mut buf = String::new();
2094              if let Err(e) = stdin().read_to_string(&mut buf) {
2095                  output.push(format!("Failed to read from stdin: {e}"));
2096                  return
2097              };
2098              buf
2099          }
2100          1 => input[0].clone(),
2101          _ => {
2102              output.push(String::from("Multiline input provided"));
2103              return
2104          }
2105      };
2106  
2107      let Some(bytes) = base64::decode(buf.trim()) else {
2108          output.push(String::from("Failed to decode encrypted proposal data"));
2109          return
2110      };
2111  
2112      let encrypted_proposal: AeadEncryptedNote = match deserialize_async(&bytes).await {
2113          Ok(e) => e,
2114          Err(e) => {
2115              output.push(format!("Failed to deserialize encrypted proposal data: {e}"));
2116              return
2117          }
2118      };
2119  
2120      let lock = drk.read().await;
2121      let daos = match lock.get_daos().await {
2122          Ok(d) => d,
2123          Err(e) => {
2124              output.push(format!("Failed to retrieve DAOs: {e}"));
2125              return
2126          }
2127      };
2128  
2129      for dao in &daos {
2130          // Check if we have the proposals key
2131          let Some(proposals_secret_key) = dao.params.proposals_secret_key else { continue };
2132  
2133          // Try to decrypt the proposal
2134          let Ok(proposal) = encrypted_proposal.decrypt::<ProposalRecord>(&proposals_secret_key)
2135          else {
2136              continue
2137          };
2138  
2139          let proposal = match lock.get_dao_proposal_by_bulla(&proposal.bulla()).await {
2140              Ok(p) => {
2141                  let mut our_proposal = p;
2142                  our_proposal.data = proposal.data;
2143                  our_proposal
2144              }
2145              Err(_) => proposal,
2146          };
2147  
2148          if let Err(e) = lock.put_dao_proposal(&proposal).await {
2149              output.push(format!("Failed to put DAO proposal: {e}"));
2150          }
2151          return
2152      }
2153  
2154      output.push(String::from("Couldn't decrypt the proposal with out DAO keys"));
2155  }
2156  
2157  /// Auxiliary function to define the dao vote subcommand handling.
2158  async fn handle_dao_vote(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2159      // Check correct subcommand structure
2160      if parts.len() != 4 && parts.len() != 5 {
2161          output.push(String::from("Malformed `dao vote` subcommand"));
2162          output.push(String::from("Usage: dao vote <bulla> <vote> [vote-weight]"));
2163          return
2164      }
2165  
2166      let bulla = match DaoProposalBulla::from_str(parts[2]) {
2167          Ok(b) => b,
2168          Err(e) => {
2169              output.push(format!("Invalid proposal bulla: {e}"));
2170              return
2171          }
2172      };
2173  
2174      let vote = match u8::from_str(parts[3]) {
2175          Ok(v) => {
2176              if v > 1 {
2177                  output.push(String::from("Vote can be either 0 (NO) or 1 (YES)"));
2178                  return
2179              }
2180              v != 0
2181          }
2182          Err(e) => {
2183              output.push(format!("Invalid vote: {e}"));
2184              return
2185          }
2186      };
2187  
2188      let weight = if parts.len() == 5 {
2189          if let Err(e) = f64::from_str(parts[4]) {
2190              output.push(format!("Invalid vote weight: {e}"));
2191              return
2192          }
2193          match decode_base10(parts[4], BALANCE_BASE10_DECIMALS, true) {
2194              Ok(w) => Some(w),
2195              Err(e) => {
2196                  output.push(format!("Error while parsing vote weight: {e}"));
2197                  return
2198              }
2199          }
2200      } else {
2201          None
2202      };
2203  
2204      match drk.read().await.dao_vote(&bulla, vote, weight).await {
2205          Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
2206          Err(e) => output.push(format!("Failed to create DAO Vote transaction: {e}")),
2207      }
2208  }
2209  
2210  /// Auxiliary function to define the dao exec subcommand handling.
2211  async fn handle_dao_exec(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2212      // Check correct subcommand structure
2213      if parts.len() != 3 && parts.len() != 4 {
2214          output.push(String::from("Malformed `dao exec` subcommand"));
2215          output.push(String::from("Usage: dao exec [--early] <bulla>"));
2216          return
2217      }
2218  
2219      let mut index = 2;
2220      let mut early = false;
2221      if parts[index] == "--early" {
2222          early = true;
2223          index += 1;
2224      }
2225  
2226      let bulla = match DaoProposalBulla::from_str(parts[index]) {
2227          Ok(b) => b,
2228          Err(e) => {
2229              output.push(format!("Invalid proposal bulla: {e}"));
2230              return
2231          }
2232      };
2233  
2234      let lock = drk.read().await;
2235      let proposal = match lock.get_dao_proposal_by_bulla(&bulla).await {
2236          Ok(p) => p,
2237          Err(e) => {
2238              output.push(format!("Failed to fetch DAO proposal: {e}"));
2239              return
2240          }
2241      };
2242  
2243      // Identify proposal type by its auth calls
2244      for call in &proposal.proposal.auth_calls {
2245          // We only support transfer right now
2246          if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
2247              match lock.dao_exec_transfer(&proposal, early).await {
2248                  Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
2249                  Err(e) => output.push(format!("Failed to execute DAO transfer proposal: {e}")),
2250              };
2251              return
2252          }
2253      }
2254  
2255      // If proposal has no auth calls, we consider it a generic one
2256      if proposal.proposal.auth_calls.is_empty() {
2257          match lock.dao_exec_generic(&proposal, early).await {
2258              Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
2259              Err(e) => output.push(format!("Failed to execute DAO generic proposal: {e}")),
2260          };
2261          return
2262      }
2263  
2264      output.push(String::from("Unsuported DAO proposal"));
2265  }
2266  
2267  /// Auxiliary function to define the dao spent hook subcommand handling.
2268  async fn handle_dao_spend_hook(parts: &[&str], output: &mut Vec<String>) {
2269      // Check correct subcommand structure
2270      if parts.len() != 2 {
2271          output.push(String::from("Malformed `dao spent-hook` subcommand"));
2272          output.push(String::from("Usage: dao spent-hook"));
2273          return
2274      }
2275  
2276      let spend_hook =
2277          FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }.to_func_id();
2278      output.push(format!("{spend_hook}"));
2279  }
2280  
2281  /// Auxiliary function to define the attach fee command handling.
2282  async fn handle_attach_fee(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
2283      let mut tx = match parse_tx_from_input(input).await {
2284          Ok(t) => t,
2285          Err(e) => {
2286              output.push(format!("Error while parsing transaction: {e}"));
2287              return
2288          }
2289      };
2290  
2291      match drk.read().await.attach_fee(&mut tx).await {
2292          Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
2293          Err(e) => output.push(format!("Failed to attach the fee call to the transaction: {e}")),
2294      }
2295  }
2296  
2297  /// Auxiliary function to define the inspect command handling.
2298  async fn handle_inspect(input: &[String], output: &mut Vec<String>) {
2299      match parse_tx_from_input(input).await {
2300          Ok(tx) => output.push(format!("{tx:#?}")),
2301          Err(e) => output.push(format!("Error while parsing transaction: {e}")),
2302      }
2303  }
2304  
2305  /// Auxiliary function to define the broadcast command handling.
2306  async fn handle_broadcast(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
2307      let tx = match parse_tx_from_input(input).await {
2308          Ok(t) => t,
2309          Err(e) => {
2310              output.push(format!("Error while parsing transaction: {e}"));
2311              return
2312          }
2313      };
2314  
2315      let lock = drk.read().await;
2316      if let Err(e) = lock.simulate_tx(&tx).await {
2317          output.push(format!("Failed to simulate tx: {e}"));
2318          return
2319      };
2320  
2321      if let Err(e) = lock.mark_tx_spend(&tx, output).await {
2322          output.push(format!("Failed to mark transaction coins as spent: {e}"));
2323          return
2324      };
2325  
2326      match lock.broadcast_tx(&tx, output).await {
2327          Ok(txid) => output.push(format!("Transaction ID: {txid}")),
2328          Err(e) => output.push(format!("Failed to broadcast transaction: {e}")),
2329      }
2330  }
2331  
2332  /// Auxiliary function to define the subscribe command handling.
2333  async fn handle_subscribe(
2334      drk: &DrkPtr,
2335      endpoint: &Url,
2336      subscription_active: &mut bool,
2337      subscription_tasks: &[StoppableTaskPtr; 2],
2338      shell_sender: &Sender<Vec<String>>,
2339      ex: &ExecutorPtr,
2340  ) {
2341      // Kill zombie tasks if they failed
2342      subscription_tasks[0].stop_nowait();
2343      subscription_tasks[1].stop_nowait();
2344      *subscription_active = true;
2345  
2346      // Start the subscription task
2347      let drk_ = drk.clone();
2348      let rpc_task_ = subscription_tasks[1].clone();
2349      let shell_sender_ = shell_sender.clone();
2350      let endpoint_ = endpoint.clone();
2351      let ex_ = ex.clone();
2352      subscription_tasks[0].clone().start(
2353          async move { subscribe_blocks(&drk_, rpc_task_, shell_sender_, endpoint_, &ex_).await },
2354          |res| async {
2355              match res {
2356                  Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
2357                  Err(e) => println!("Failed starting subscription task: {e}"),
2358              }
2359          },
2360          Error::DetachedTaskStopped,
2361          ex.clone(),
2362      );
2363  }
2364  
2365  /// Auxiliary function to define the unsubscribe command handling.
2366  async fn handle_unsubscribe(
2367      subscription_active: &mut bool,
2368      subscription_tasks: &[StoppableTaskPtr; 2],
2369  ) {
2370      subscription_tasks[0].stop_nowait();
2371      subscription_tasks[1].stop_nowait();
2372      *subscription_active = false;
2373  }
2374  
2375  /// Auxiliary function to define the scan command handling.
2376  async fn handle_scan(
2377      drk: &DrkPtr,
2378      subscription_active: &bool,
2379      parts: &[&str],
2380      output: &mut Vec<String>,
2381      print: &bool,
2382  ) {
2383      if *subscription_active {
2384          append_or_print(output, None, print, vec![String::from("Subscription is already active!")])
2385              .await;
2386          return
2387      }
2388  
2389      // Check correct command structure
2390      if parts.len() != 1 && parts.len() != 3 {
2391          append_or_print(output, None, print, vec![String::from("Malformed `scan` command")]).await;
2392          return
2393      }
2394  
2395      // Check if reset was requested
2396      let lock = drk.read().await;
2397      if parts.len() == 3 {
2398          if parts[1] != "--reset" {
2399              append_or_print(
2400                  output,
2401                  None,
2402                  print,
2403                  vec![
2404                      String::from("Malformed `scan` command"),
2405                      String::from("Usage: scan --reset <height>"),
2406                  ],
2407              )
2408              .await;
2409              return
2410          }
2411  
2412          let height = match u32::from_str(parts[2]) {
2413              Ok(h) => h,
2414              Err(e) => {
2415                  append_or_print(output, None, print, vec![format!("Invalid reset height: {e}")])
2416                      .await;
2417                  return
2418              }
2419          };
2420  
2421          let mut buf = vec![];
2422          if let Err(e) = lock.reset_to_height(height, &mut buf).await {
2423              buf.push(format!("Failed during wallet reset: {e}"));
2424              append_or_print(output, None, print, buf).await;
2425              return
2426          }
2427          append_or_print(output, None, print, buf).await;
2428      }
2429  
2430      if let Err(e) = lock.scan_blocks(output, None, print).await {
2431          append_or_print(output, None, print, vec![format!("Failed during scanning: {e}")]).await;
2432          return
2433      }
2434      append_or_print(output, None, print, vec![String::from("Finished scanning blockchain")]).await;
2435  }
2436  
2437  /// Auxiliary function to define the explorer command handling.
2438  async fn handle_explorer(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
2439      // Check correct command structure
2440      if parts.len() < 2 {
2441          output.push(String::from("Malformed `explorer` command"));
2442          output.push(String::from(
2443              "Usage: explorer (fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks)",
2444          ));
2445          return
2446      }
2447  
2448      // Handle subcommand
2449      match parts[1] {
2450          "fetch-tx" => handle_explorer_fetch_tx(drk, parts, output).await,
2451          "simulate-tx" => handle_explorer_simulate_tx(drk, parts, input, output).await,
2452          "txs-history" => handle_explorer_txs_history(drk, parts, output).await,
2453          "clear-reverted" => handle_explorer_clear_reverted(drk, parts, output).await,
2454          "scanned-blocks" => handle_explorer_scanned_blocks(drk, parts, output).await,
2455          _ => {
2456              output.push(format!("Unrecognized explorer subcommand: {}", parts[1]));
2457              output.push(String::from(
2458                  "Usage: explorer (fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks)",
2459              ));
2460          }
2461      }
2462  }
2463  
2464  /// Auxiliary function to define the explorer fetch transaction subcommand handling.
2465  async fn handle_explorer_fetch_tx(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2466      // Check correct subcommand structure
2467      if parts.len() != 3 && parts.len() != 4 {
2468          output.push(String::from("Malformed `explorer fetch-tx` subcommand"));
2469          output.push(String::from("Usage: explorer fetch-tx [--encode] <tx-hash>"));
2470          return
2471      }
2472  
2473      let mut index = 2;
2474      let mut encode = false;
2475      if parts[index] == "--encode" {
2476          encode = true;
2477          index += 1;
2478      }
2479  
2480      let hash = match blake3::Hash::from_hex(parts[index]) {
2481          Ok(h) => h,
2482          Err(e) => {
2483              output.push(format!("Invalid transaction hash: {e}"));
2484              return
2485          }
2486      };
2487      let tx_hash = TransactionHash(*hash.as_bytes());
2488  
2489      let tx = match drk.read().await.get_tx(&tx_hash).await {
2490          Ok(tx) => tx,
2491          Err(e) => {
2492              output.push(format!("Failed to fetch transaction: {e}"));
2493              return
2494          }
2495      };
2496  
2497      let Some(tx) = tx else {
2498          output.push(String::from("Transaction was not found"));
2499          return
2500      };
2501  
2502      // Make sure the tx is correct
2503      if tx.hash() != tx_hash {
2504          output.push(format!("Transaction hash missmatch: {tx_hash} - {}", tx.hash()));
2505          return
2506      }
2507  
2508      if encode {
2509          output.push(base64::encode(&serialize_async(&tx).await));
2510          return
2511      }
2512  
2513      output.push(format!("Transaction ID: {tx_hash}"));
2514      output.push(format!("{tx:?}"));
2515  }
2516  
2517  /// Auxiliary function to define the explorer simulate transaction subcommand handling.
2518  async fn handle_explorer_simulate_tx(
2519      drk: &DrkPtr,
2520      parts: &[&str],
2521      input: &[String],
2522      output: &mut Vec<String>,
2523  ) {
2524      // Check correct subcommand structure
2525      if parts.len() != 2 {
2526          output.push(String::from("Malformed `explorer simulate-tx` subcommand"));
2527          output.push(String::from("Usage: explorer simulate-tx"));
2528          return
2529      }
2530  
2531      let tx = match parse_tx_from_input(input).await {
2532          Ok(t) => t,
2533          Err(e) => {
2534              output.push(format!("Error while parsing transaction: {e}"));
2535              return
2536          }
2537      };
2538  
2539      match drk.read().await.simulate_tx(&tx).await {
2540          Ok(is_valid) => {
2541              output.push(format!("Transaction ID: {}", tx.hash()));
2542              output.push(format!("State: {}", if is_valid { "valid" } else { "invalid" }));
2543          }
2544          Err(e) => output.push(format!("Failed to simulate tx: {e}")),
2545      }
2546  }
2547  
2548  /// Auxiliary function to define the explorer transactions history subcommand handling.
2549  async fn handle_explorer_txs_history(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2550      // Check correct command structure
2551      if parts.len() < 2 || parts.len() > 4 {
2552          output.push(String::from("Malformed `explorer txs-history` command"));
2553          output.push(String::from("Usage: explorer txs-history [--encode] [tx-hash]"));
2554          return
2555      }
2556  
2557      let lock = drk.read().await;
2558      if parts.len() > 2 {
2559          let mut index = 2;
2560          let mut encode = false;
2561          if parts[index] == "--encode" {
2562              encode = true;
2563              index += 1;
2564          }
2565  
2566          let (tx_hash, status, block_height, tx) =
2567              match lock.get_tx_history_record(parts[index]).await {
2568                  Ok(i) => i,
2569                  Err(e) => {
2570                      output.push(format!("Failed to fetch transaction: {e}"));
2571                      return
2572                  }
2573              };
2574  
2575          if encode {
2576              output.push(base64::encode(&serialize_async(&tx).await));
2577              return
2578          }
2579  
2580          output.push(format!("Transaction ID: {tx_hash}"));
2581          output.push(format!("Status: {status}"));
2582          match block_height {
2583              Some(block_height) => output.push(format!("Block height: {block_height}")),
2584              None => output.push(String::from("Block height: -")),
2585          }
2586          output.push(format!("{tx:?}"));
2587          return
2588      }
2589  
2590      let map = match lock.get_txs_history() {
2591          Ok(m) => m,
2592          Err(e) => {
2593              output.push(format!("Failed to retrieve transactions history records: {e}"));
2594              return
2595          }
2596      };
2597  
2598      // Create a prettytable with the new data:
2599      let mut table = Table::new();
2600      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2601      table.set_titles(row!["Transaction Hash", "Status", "Block Height"]);
2602      for (txs_hash, status, block_height) in map.iter() {
2603          let block_height = match block_height {
2604              Some(block_height) => block_height.to_string(),
2605              None => String::from("-"),
2606          };
2607          table.add_row(row![txs_hash, status, block_height]);
2608      }
2609  
2610      if table.is_empty() {
2611          output.push(String::from("No transactions found"));
2612      } else {
2613          output.push(format!("{table}"));
2614      }
2615  }
2616  
2617  /// Auxiliary function to define the explorer clear reverted subcommand handling.
2618  async fn handle_explorer_clear_reverted(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2619      // Check correct subcommand structure
2620      if parts.len() != 2 {
2621          output.push(String::from("Malformed `explorer clear-reverted` subcommand"));
2622          output.push(String::from("Usage: explorer clear-reverted"));
2623          return
2624      }
2625  
2626      if let Err(e) = drk.read().await.remove_reverted_txs(output) {
2627          output.push(format!("Failed to remove reverted transactions: {e}"));
2628      }
2629  }
2630  
2631  /// Auxiliary function to define the explorer scanned blocks subcommand handling.
2632  async fn handle_explorer_scanned_blocks(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2633      // Check correct subcommand structure
2634      if parts.len() != 2 && parts.len() != 3 {
2635          output.push(String::from("Malformed `explorer scanned-blocks` subcommand"));
2636          output.push(String::from("Usage: explorer scanned-blocks [height]"));
2637          return
2638      }
2639  
2640      let lock = drk.read().await;
2641      if parts.len() == 3 {
2642          let height = match u32::from_str(parts[2]) {
2643              Ok(d) => d,
2644              Err(e) => {
2645                  output.push(format!("Invalid height: {e}"));
2646                  return
2647              }
2648          };
2649  
2650          match lock.get_scanned_block_hash(&height) {
2651              Ok(hash) => {
2652                  output.push(format!("Height: {height}"));
2653                  output.push(format!("Hash: {hash}"));
2654              }
2655              Err(e) => output.push(format!("Failed to retrieve scanned block record: {e}")),
2656          };
2657          return
2658      }
2659  
2660      let map = match lock.get_scanned_block_records() {
2661          Ok(m) => m,
2662          Err(e) => {
2663              output.push(format!("Failed to retrieve scanned blocks records: {e}"));
2664              return
2665          }
2666      };
2667  
2668      // Create a prettytable with the new data:
2669      let mut table = Table::new();
2670      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2671      table.set_titles(row!["Height", "Hash"]);
2672      for (height, hash) in map.iter() {
2673          table.add_row(row![height, hash]);
2674      }
2675  
2676      if table.is_empty() {
2677          output.push(String::from("No scanned blocks records found"));
2678      } else {
2679          output.push(format!("{table}"));
2680      }
2681  }
2682  
2683  /// Auxiliary function to define the alias command handling.
2684  async fn handle_alias(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2685      // Check correct command structure
2686      if parts.len() < 2 {
2687          output.push(String::from("Malformed `alias` command"));
2688          output.push(String::from("Usage: alias (add|show|remove)"));
2689          return
2690      }
2691  
2692      // Handle subcommand
2693      match parts[1] {
2694          "add" => handle_alias_add(drk, parts, output).await,
2695          "show" => handle_alias_show(drk, parts, output).await,
2696          "remove" => handle_alias_remove(drk, parts, output).await,
2697          _ => {
2698              output.push(format!("Unrecognized alias subcommand: {}", parts[1]));
2699              output.push(String::from("Usage: alias (add|show|remove)"));
2700          }
2701      }
2702  }
2703  
2704  /// Auxiliary function to define the alias add subcommand handling.
2705  async fn handle_alias_add(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2706      // Check correct subcommand structure
2707      if parts.len() != 4 {
2708          output.push(String::from("Malformed `alias add` subcommand"));
2709          output.push(String::from("Usage: alias add <alias> <token>"));
2710          return
2711      }
2712  
2713      if parts[2].len() > 5 {
2714          output.push(String::from("Error: Alias exceeds 5 characters"));
2715          return
2716      }
2717  
2718      let token_id = match TokenId::from_str(parts[3]) {
2719          Ok(t) => t,
2720          Err(e) => {
2721              output.push(format!("Invalid Token ID: {e}"));
2722              return
2723          }
2724      };
2725  
2726      if let Err(e) = drk.read().await.add_alias(String::from(parts[2]), token_id, output).await {
2727          output.push(format!("Failed to add alias: {e}"));
2728      }
2729  }
2730  
2731  /// Auxiliary function to define the alias show subcommand handling.
2732  async fn handle_alias_show(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2733      // Check correct command structure
2734      if parts.len() != 2 && parts.len() != 4 && parts.len() != 6 {
2735          output.push(String::from("Malformed `alias show` command"));
2736          output.push(String::from("Usage: alias show [-a, --alias <alias>] [-t, --token <token>]"));
2737          return
2738      }
2739  
2740      let mut alias = None;
2741      let mut token_id = None;
2742      if parts.len() > 2 {
2743          let mut index = 2;
2744          if parts[index] == "-a" || parts[index] == "--alias" {
2745              alias = Some(String::from(parts[index + 1]));
2746              index += 2;
2747          }
2748  
2749          if index < parts.len() && (parts[index] == "-t" || parts[index] == "--token") {
2750              match TokenId::from_str(parts[index + 1]) {
2751                  Ok(t) => token_id = Some(t),
2752                  Err(e) => {
2753                      output.push(format!("Invalid Token ID: {e}"));
2754                      return
2755                  }
2756              };
2757              index += 2;
2758          }
2759  
2760          // Check alias again in case it was after token
2761          if index < parts.len() && (parts[index] == "-a" || parts[index] == "--alias") {
2762              alias = Some(String::from(parts[index + 1]));
2763          }
2764      }
2765  
2766      let map = match drk.read().await.get_aliases(alias, token_id).await {
2767          Ok(m) => m,
2768          Err(e) => {
2769              output.push(format!("Failed to fetch aliases map: {e}"));
2770              return
2771          }
2772      };
2773  
2774      // Create a prettytable with the new data:
2775      let mut table = Table::new();
2776      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2777      table.set_titles(row!["Alias", "Token ID"]);
2778      for (alias, token_id) in map.iter() {
2779          table.add_row(row![alias, token_id]);
2780      }
2781  
2782      if table.is_empty() {
2783          output.push(String::from("No aliases found"));
2784      } else {
2785          output.push(format!("{table}"));
2786      }
2787  }
2788  
2789  /// Auxiliary function to define the alias remove subcommand handling.
2790  async fn handle_alias_remove(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2791      // Check correct subcommand structure
2792      if parts.len() != 3 {
2793          output.push(String::from("Malformed `alias remove` subcommand"));
2794          output.push(String::from("Usage: alias remove <alias>"));
2795          return
2796      }
2797  
2798      if let Err(e) = drk.read().await.remove_alias(String::from(parts[2]), output).await {
2799          output.push(format!("Failed to remove alias: {e}"));
2800      }
2801  }
2802  
2803  /// Auxiliary function to define the token command handling.
2804  async fn handle_token(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2805      // Check correct command structure
2806      if parts.len() < 2 {
2807          output.push(String::from("Malformed `token` command"));
2808          output.push(String::from("Usage: token (import|generate-mint|list|mint|freeze)"));
2809          return
2810      }
2811  
2812      // Handle subcommand
2813      match parts[1] {
2814          "import" => handle_token_import(drk, parts, output).await,
2815          "generate-mint" => handle_token_generate_mint(drk, parts, output).await,
2816          "list" => handle_token_list(drk, parts, output).await,
2817          "mint" => handle_token_mint(drk, parts, output).await,
2818          "freeze" => handle_token_freeze(drk, parts, output).await,
2819          _ => {
2820              output.push(format!("Unrecognized token subcommand: {}", parts[1]));
2821              output.push(String::from("Usage: token (import|generate-mint|list|mint|freeze)"));
2822          }
2823      }
2824  }
2825  
2826  /// Auxiliary function to define the token import subcommand handling.
2827  async fn handle_token_import(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2828      // Check correct subcommand structure
2829      if parts.len() != 4 {
2830          output.push(String::from("Malformed `token import` subcommand"));
2831          output.push(String::from("Usage: token import <secret-key> <token-blind>"));
2832          return
2833      }
2834  
2835      let mint_authority = match SecretKey::from_str(parts[2]) {
2836          Ok(ma) => ma,
2837          Err(e) => {
2838              output.push(format!("Invalid mint authority: {e}"));
2839              return
2840          }
2841      };
2842  
2843      let token_blind = match BaseBlind::from_str(parts[3]) {
2844          Ok(tb) => tb,
2845          Err(e) => {
2846              output.push(format!("Invalid token blind: {e}"));
2847              return
2848          }
2849      };
2850  
2851      match drk.read().await.import_mint_authority(mint_authority, token_blind).await {
2852          Ok(token_id) => {
2853              output.push(format!("Successfully imported mint authority for token ID: {token_id}"))
2854          }
2855          Err(e) => output.push(format!("Failed to import mint authority: {e}")),
2856      }
2857  }
2858  
2859  /// Auxiliary function to define the token generate mint subcommand handling.
2860  async fn handle_token_generate_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2861      // Check correct subcommand structure
2862      if parts.len() != 2 {
2863          output.push(String::from("Malformed `token generate-mint` subcommand"));
2864          output.push(String::from("Usage: token generate-mint"));
2865          return
2866      }
2867  
2868      let mint_authority = SecretKey::random(&mut OsRng);
2869      let token_blind = BaseBlind::random(&mut OsRng);
2870      match drk.read().await.import_mint_authority(mint_authority, token_blind).await {
2871          Ok(token_id) => {
2872              output.push(format!("Successfully imported mint authority for token ID: {token_id}"))
2873          }
2874          Err(e) => output.push(format!("Failed to import mint authority: {e}")),
2875      }
2876  }
2877  
2878  /// Auxiliary function to define the token list subcommand handling.
2879  async fn handle_token_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2880      // Check correct subcommand structure
2881      if parts.len() != 2 {
2882          output.push(String::from("Malformed `token list` subcommand"));
2883          output.push(String::from("Usage: token list"));
2884          return
2885      }
2886  
2887      let lock = drk.read().await;
2888      let tokens = match lock.get_mint_authorities().await {
2889          Ok(m) => m,
2890          Err(e) => {
2891              output.push(format!("Failed to fetch mint authorities: {e}"));
2892              return
2893          }
2894      };
2895  
2896      let aliases_map = match lock.get_aliases_mapped_by_token().await {
2897          Ok(m) => m,
2898          Err(e) => {
2899              output.push(format!("Failed to fetch aliases map: {e}"));
2900              return
2901          }
2902      };
2903  
2904      let mut table = Table::new();
2905      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2906      table.set_titles(row![
2907          "Token ID",
2908          "Aliases",
2909          "Mint Authority",
2910          "Token Blind",
2911          "Frozen",
2912          "Freeze Height"
2913      ]);
2914  
2915      for (token_id, authority, blind, frozen, freeze_height) in tokens {
2916          let aliases = match aliases_map.get(&token_id.to_string()) {
2917              Some(a) => a,
2918              None => "-",
2919          };
2920  
2921          let freeze_height = match freeze_height {
2922              Some(freeze_height) => freeze_height.to_string(),
2923              None => String::from("-"),
2924          };
2925  
2926          table.add_row(row![token_id, aliases, authority, blind, frozen, freeze_height]);
2927      }
2928  
2929      if table.is_empty() {
2930          output.push(String::from("No tokens found"));
2931      } else {
2932          output.push(format!("{table}"));
2933      }
2934  }
2935  
2936  /// Auxiliary function to define the token mint subcommand handling.
2937  async fn handle_token_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2938      // Check correct command structure
2939      if parts.len() < 5 || parts.len() > 7 {
2940          output.push(String::from("Malformed `token mint` subcommand"));
2941          output.push(String::from(
2942              "Usage: token mint <token> <amount> <recipient> [spend-hook] [user-data]",
2943          ));
2944          return
2945      }
2946  
2947      let amount = String::from(parts[3]);
2948      if let Err(e) = f64::from_str(&amount) {
2949          output.push(format!("Invalid amount: {e}"));
2950          return
2951      }
2952  
2953      let rcpt = match PublicKey::from_str(parts[4]) {
2954          Ok(r) => r,
2955          Err(e) => {
2956              output.push(format!("Invalid recipient: {e}"));
2957              return
2958          }
2959      };
2960  
2961      let lock = drk.read().await;
2962      let token_id = match lock.get_token(String::from(parts[2])).await {
2963          Ok(t) => t,
2964          Err(e) => {
2965              output.push(format!("Invalid token ID: {e}"));
2966              return
2967          }
2968      };
2969  
2970      // Parse command
2971      let mut index = 5;
2972      let spend_hook = if index < parts.len() {
2973          match FuncId::from_str(parts[index]) {
2974              Ok(s) => Some(s),
2975              Err(e) => {
2976                  output.push(format!("Invalid spend hook: {e}"));
2977                  return
2978              }
2979          }
2980      } else {
2981          None
2982      };
2983      index += 1;
2984  
2985      let user_data = if index < parts.len() {
2986          let bytes = match bs58::decode(&parts[index]).into_vec() {
2987              Ok(b) => b,
2988              Err(e) => {
2989                  output.push(format!("Invalid user data: {e}"));
2990                  return
2991              }
2992          };
2993  
2994          let bytes: [u8; 32] = match bytes.try_into() {
2995              Ok(b) => b,
2996              Err(e) => {
2997                  output.push(format!("Invalid user data: {e:?}"));
2998                  return
2999              }
3000          };
3001  
3002          let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
3003              Some(v) => v,
3004              None => {
3005                  output.push(String::from("Invalid user data"));
3006                  return
3007              }
3008          };
3009  
3010          Some(elem)
3011      } else {
3012          None
3013      };
3014  
3015      match lock.mint_token(&amount, rcpt, token_id, spend_hook, user_data).await {
3016          Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3017          Err(e) => output.push(format!("Failed to create token mint transaction: {e}")),
3018      }
3019  }
3020  
3021  /// Auxiliary function to define the token freeze subcommand handling.
3022  async fn handle_token_freeze(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3023      // Check correct subcommand structure
3024      if parts.len() != 3 {
3025          output.push(String::from("Malformed `token freeze` subcommand"));
3026          output.push(String::from("Usage: token freeze <token>"));
3027          return
3028      }
3029  
3030      let lock = drk.read().await;
3031      let token_id = match lock.get_token(String::from(parts[2])).await {
3032          Ok(t) => t,
3033          Err(e) => {
3034              output.push(format!("Invalid token ID: {e}"));
3035              return
3036          }
3037      };
3038  
3039      match lock.freeze_token(token_id).await {
3040          Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3041          Err(e) => output.push(format!("Failed to create token freeze transaction: {e}")),
3042      }
3043  }
3044  
3045  /// Auxiliary function to define the contract command handling.
3046  async fn handle_contract(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3047      // Check correct command structure
3048      if parts.len() < 2 {
3049          output.push(String::from("Malformed `contract` command"));
3050          output.push(String::from("Usage: contract (generate-deploy|list|export-data|deploy|lock)"));
3051          return
3052      }
3053  
3054      // Handle subcommand
3055      match parts[1] {
3056          "generate-deploy" => handle_contract_generate_deploy(drk, parts, output).await,
3057          "list" => handle_contract_list(drk, parts, output).await,
3058          "export-data" => handle_contract_export_data(drk, parts, output).await,
3059          "deploy" => handle_contract_deploy(drk, parts, output).await,
3060          "lock" => handle_contract_lock(drk, parts, output).await,
3061          _ => {
3062              output.push(format!("Unrecognized contract subcommand: {}", parts[1]));
3063              output.push(String::from(
3064                  "Usage: contract (generate-deploy|list|export-data|deploy|lock)",
3065              ));
3066          }
3067      }
3068  }
3069  
3070  /// Auxiliary function to define the contract generate deploy subcommand handling.
3071  async fn handle_contract_generate_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3072      // Check correct subcommand structure
3073      if parts.len() != 2 {
3074          output.push(String::from("Malformed `contract generate-deploy` subcommand"));
3075          output.push(String::from("Usage: contract generate-deploy"));
3076          return
3077      }
3078  
3079      if let Err(e) = drk.read().await.deploy_auth_keygen(output).await {
3080          output.push(format!("Error creating deploy auth keypair: {e}"));
3081      }
3082  }
3083  
3084  /// Auxiliary function to define the contract list subcommand handling.
3085  async fn handle_contract_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3086      // Check correct subcommand structure
3087      if parts.len() != 2 && parts.len() != 3 {
3088          output.push(String::from("Malformed `contract list` subcommand"));
3089          output.push(String::from("Usage: contract list [contract-id]"));
3090          return
3091      }
3092  
3093      if parts.len() == 3 {
3094          let deploy_auth = match ContractId::from_str(parts[2]) {
3095              Ok(d) => d,
3096              Err(e) => {
3097                  output.push(format!("Invalid deploy authority: {e}"));
3098                  return
3099              }
3100          };
3101  
3102          let history = match drk.read().await.get_deploy_auth_history(&deploy_auth).await {
3103              Ok(a) => a,
3104              Err(e) => {
3105                  output.push(format!("Failed to fetch deploy authority history records: {e}"));
3106                  return
3107              }
3108          };
3109  
3110          let mut table = Table::new();
3111          table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
3112          table.set_titles(row!["Transaction Hash", "Type", "Block Height"]);
3113  
3114          for (tx_hash, tx_type, block_height) in history {
3115              table.add_row(row![tx_hash, tx_type, block_height]);
3116          }
3117  
3118          if table.is_empty() {
3119              output.push(String::from("No history records found"));
3120          } else {
3121              output.push(format!("{table}"));
3122          }
3123          return
3124      }
3125  
3126      let auths = match drk.read().await.list_deploy_auth().await {
3127          Ok(a) => a,
3128          Err(e) => {
3129              output.push(format!("Failed to fetch deploy authorities: {e}"));
3130              return
3131          }
3132      };
3133  
3134      let mut table = Table::new();
3135      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
3136      table.set_titles(row!["Contract ID", "Secret Key", "Locked", "Lock Height"]);
3137  
3138      for (contract_id, secret_key, is_locked, lock_height) in auths {
3139          let lock_height = match lock_height {
3140              Some(lock_height) => lock_height.to_string(),
3141              None => String::from("-"),
3142          };
3143          table.add_row(row![contract_id, secret_key, is_locked, lock_height]);
3144      }
3145  
3146      if table.is_empty() {
3147          output.push(String::from("No deploy authorities found"));
3148      } else {
3149          output.push(format!("{table}"));
3150      }
3151  }
3152  
3153  /// Auxiliary function to define the contract export data subcommand handling.
3154  async fn handle_contract_export_data(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3155      // Check correct subcommand structure
3156      if parts.len() != 3 {
3157          output.push(String::from("Malformed `contract export-data` subcommand"));
3158          output.push(String::from("Usage: contract export-data <tx-hash>"));
3159          return
3160      }
3161  
3162      match drk.read().await.get_deploy_history_record_data(parts[2]).await {
3163          Ok(pair) => output.push(base64::encode(&serialize_async(&pair).await)),
3164          Err(e) => output.push(format!("Failed to retrieve history record: {e}")),
3165      }
3166  }
3167  
3168  /// Auxiliary function to define the contract deploy subcommand handling.
3169  async fn handle_contract_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3170      // Check correct subcommand structure
3171      if parts.len() != 4 && parts.len() != 5 {
3172          output.push(String::from("Malformed `contract deploy` subcommand"));
3173          output.push(String::from("Usage: contract deploy <deploy-auth> <wasm-path> [deploy-ix]"));
3174          return
3175      }
3176  
3177      let deploy_auth = match ContractId::from_str(parts[2]) {
3178          Ok(d) => d,
3179          Err(e) => {
3180              output.push(format!("Invalid deploy authority: {e}"));
3181              return
3182          }
3183      };
3184  
3185      // Read the wasm bincode and deploy instruction
3186      let file_path = match expand_path(parts[3]) {
3187          Ok(p) => p,
3188          Err(e) => {
3189              output.push(format!("Error while expanding wasm bincode file path: {e}"));
3190              return
3191          }
3192      };
3193      let wasm_bin = match smol::fs::read(file_path).await {
3194          Ok(w) => w,
3195          Err(e) => {
3196              output.push(format!("Error while reading wasm bincode file: {e}"));
3197              return
3198          }
3199      };
3200  
3201      let deploy_ix = if parts.len() == 5 {
3202          let file_path = match expand_path(parts[4]) {
3203              Ok(p) => p,
3204              Err(e) => {
3205                  output.push(format!("Error while expanding deploy instruction file path: {e}"));
3206                  return
3207              }
3208          };
3209          match smol::fs::read(file_path).await {
3210              Ok(d) => d,
3211              Err(e) => {
3212                  output.push(format!("Error while reading deploy instruction file: {e}"));
3213                  return
3214              }
3215          }
3216      } else {
3217          vec![]
3218      };
3219  
3220      match drk.read().await.deploy_contract(&deploy_auth, wasm_bin, deploy_ix).await {
3221          Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3222          Err(e) => output.push(format!("Failed to create contract deployment transaction: {e}")),
3223      }
3224  }
3225  
3226  /// Auxiliary function to define the contract lock subcommand handling.
3227  async fn handle_contract_lock(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3228      // Check correct subcommand structure
3229      if parts.len() != 3 {
3230          output.push(String::from("Malformed `contract lock` subcommand"));
3231          output.push(String::from("Usage: contract lock <deploy-auth>"));
3232          return
3233      }
3234  
3235      let deploy_auth = match ContractId::from_str(parts[2]) {
3236          Ok(d) => d,
3237          Err(e) => {
3238              output.push(format!("Invalid deploy authority: {e}"));
3239              return
3240          }
3241      };
3242  
3243      match drk.read().await.lock_contract(&deploy_auth).await {
3244          Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3245          Err(e) => output.push(format!("Failed to create contract lock transaction: {e}")),
3246      }
3247  }