/ bin / drk / src / main.rs
main.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      io::{stdin, Read},
  21      process::exit,
  22      str::FromStr,
  23  };
  24  
  25  use prettytable::{format, row, Table};
  26  use rand::rngs::OsRng;
  27  use smol::{fs::read_to_string, stream::StreamExt};
  28  use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  29  use tracing::info;
  30  use url::Url;
  31  
  32  use darkfi::{
  33      async_daemonize, cli_desc,
  34      system::ExecutorPtr,
  35      util::{
  36          encoding::base64,
  37          parse::{decode_base10, encode_base10},
  38          path::{expand_path, get_config_path},
  39      },
  40      zk::halo2::Field,
  41      Error, Result,
  42  };
  43  use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
  44  use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
  45  use darkfi_sdk::{
  46      crypto::{
  47          note::AeadEncryptedNote, BaseBlind, ContractId, FuncId, FuncRef, Keypair, PublicKey,
  48          SecretKey, DAO_CONTRACT_ID,
  49      },
  50      pasta::{group::ff::PrimeField, pallas},
  51      tx::TransactionHash,
  52  };
  53  use darkfi_serial::{deserialize_async, serialize_async};
  54  
  55  use drk::{
  56      cli_util::{
  57          generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair,
  58          print_output,
  59      },
  60      dao::{DaoParams, ProposalRecord},
  61      interactive::interactive,
  62      money::BALANCE_BASE10_DECIMALS,
  63      swap::PartialSwapData,
  64      Drk,
  65  };
  66  
  67  const CONFIG_FILE: &str = "drk_config.toml";
  68  const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
  69  
  70  // Dev Note: when adding/modifying args here,
  71  // don't forget to update cli_util::generate_completions()
  72  // and interactive::help().
  73  #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  74  #[serde(default)]
  75  #[structopt(name = "drk", about = cli_desc!())]
  76  struct Args {
  77      #[structopt(short, long)]
  78      /// Configuration file to use
  79      config: Option<String>,
  80  
  81      #[structopt(short, long, default_value = "testnet")]
  82      /// Blockchain network to use
  83      network: String,
  84  
  85      #[structopt(subcommand)]
  86      /// Sub command to execute
  87      command: Subcmd,
  88  
  89      #[structopt(short, long)]
  90      /// Flag indicating whether you want some fun in your life
  91      fun: bool,
  92  
  93      #[structopt(short, long)]
  94      /// Set log file to ouput into
  95      log: Option<String>,
  96  
  97      #[structopt(short, parse(from_occurrences))]
  98      /// Increase verbosity (-vvv supported)
  99      verbose: u8,
 100  }
 101  
 102  // Dev Note: when adding/modifying commands here,
 103  // don't forget to update cli_util::generate_completions()
 104  #[derive(Clone, Debug, Deserialize, StructOpt)]
 105  enum Subcmd {
 106      /// Enter Drk interactive shell
 107      Interactive,
 108  
 109      /// Fun
 110      Kaching,
 111  
 112      /// Send a ping request to the darkfid RPC endpoint
 113      Ping,
 114  
 115      /// Generate a SHELL completion script and print to stdout
 116      Completions {
 117          /// The Shell you want to generate script for
 118          shell: String,
 119      },
 120  
 121      /// Wallet operations
 122      Wallet {
 123          #[structopt(subcommand)]
 124          /// Sub command to execute
 125          command: WalletSubcmd,
 126      },
 127  
 128      /// Read a transaction from stdin and mark its input coins as spent
 129      Spend,
 130  
 131      /// Unspend a coin
 132      Unspend {
 133          /// base64-encoded coin to mark as unspent
 134          coin: String,
 135      },
 136  
 137      /// Create a payment transaction
 138      Transfer {
 139          /// Amount to send
 140          amount: String,
 141  
 142          /// Token ID to send
 143          token: String,
 144  
 145          /// Recipient address
 146          recipient: String,
 147  
 148          /// Optional contract spend hook to use
 149          spend_hook: Option<String>,
 150  
 151          /// Optional user data to use
 152          user_data: Option<String>,
 153  
 154          #[structopt(long)]
 155          /// Split the output coin into two equal halves
 156          half_split: bool,
 157      },
 158  
 159      /// OTC atomic swap
 160      Otc {
 161          #[structopt(subcommand)]
 162          /// Sub command to execute
 163          command: OtcSubcmd,
 164      },
 165  
 166      /// DAO functionalities
 167      Dao {
 168          #[structopt(subcommand)]
 169          /// Sub command to execute
 170          command: DaoSubcmd,
 171      },
 172  
 173      /// Attach the fee call to a transaction given from stdin
 174      AttachFee,
 175  
 176      /// Inspect a transaction from stdin
 177      Inspect,
 178  
 179      /// Read a transaction from stdin and broadcast it
 180      Broadcast,
 181  
 182      /// Scan the blockchain and parse relevant transactions
 183      Scan {
 184          #[structopt(long)]
 185          /// Reset wallet state to provided block height and start scanning
 186          reset: Option<u32>,
 187      },
 188  
 189      /// Explorer related subcommands
 190      Explorer {
 191          #[structopt(subcommand)]
 192          /// Sub command to execute
 193          command: ExplorerSubcmd,
 194      },
 195  
 196      /// Manage Token aliases
 197      Alias {
 198          #[structopt(subcommand)]
 199          /// Sub command to execute
 200          command: AliasSubcmd,
 201      },
 202  
 203      /// Token functionalities
 204      Token {
 205          #[structopt(subcommand)]
 206          /// Sub command to execute
 207          command: TokenSubcmd,
 208      },
 209  
 210      /// Contract functionalities
 211      Contract {
 212          #[structopt(subcommand)]
 213          /// Sub command to execute
 214          command: ContractSubcmd,
 215      },
 216  }
 217  
 218  #[derive(Clone, Debug, Deserialize, StructOpt)]
 219  enum WalletSubcmd {
 220      /// Initialize wallet database
 221      Initialize,
 222  
 223      /// Generate a new keypair in the wallet
 224      Keygen,
 225  
 226      /// Query the wallet for known balances
 227      Balance,
 228  
 229      /// Get the default address in the wallet
 230      Address,
 231  
 232      /// Print all the addresses in the wallet
 233      Addresses,
 234  
 235      /// Set the default address in the wallet
 236      DefaultAddress {
 237          /// Identifier of the address
 238          index: usize,
 239      },
 240  
 241      /// Print all the secret keys from the wallet
 242      Secrets,
 243  
 244      /// Import secret keys from stdin into the wallet, separated by newlines
 245      ImportSecrets,
 246  
 247      /// Print the Merkle tree in the wallet
 248      Tree,
 249  
 250      /// Print all the coins in the wallet
 251      Coins,
 252  }
 253  
 254  #[derive(Clone, Debug, Deserialize, StructOpt)]
 255  enum OtcSubcmd {
 256      /// Initialize the first half of the atomic swap
 257      Init {
 258          /// Value pair to send:recv (11.55:99.42)
 259          #[structopt(short, long)]
 260          value_pair: String,
 261  
 262          /// Token pair to send:recv (f00:b4r)
 263          #[structopt(short, long)]
 264          token_pair: String,
 265      },
 266  
 267      /// Build entire swap tx given the first half from stdin
 268      Join,
 269  
 270      /// Inspect a swap half or the full swap tx from stdin
 271      Inspect,
 272  
 273      /// Sign a swap transaction given from stdin
 274      Sign,
 275  }
 276  
 277  #[derive(Clone, Debug, Deserialize, StructOpt)]
 278  enum DaoSubcmd {
 279      /// Create DAO parameters
 280      Create {
 281          /// The minimum amount of governance tokens needed to open a proposal for this DAO
 282          proposer_limit: String,
 283          /// Minimal threshold of participating total tokens needed for a proposal to pass
 284          quorum: String,
 285          /// Minimal threshold of participating total tokens needed for a proposal to
 286          /// be considered as strongly supported, enabling early execution.
 287          /// Must be greater or equal to normal quorum.
 288          early_exec_quorum: String,
 289          /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)
 290          approval_ratio: f64,
 291          /// DAO's governance token ID
 292          gov_token_id: String,
 293      },
 294  
 295      /// View DAO data from stdin
 296      View,
 297  
 298      /// Import DAO data from stdin
 299      Import {
 300          /// Name identifier for the DAO
 301          name: String,
 302      },
 303  
 304      /// List imported DAOs (or info about a specific one)
 305      List {
 306          /// Name identifier for the DAO (optional)
 307          name: Option<String>,
 308      },
 309  
 310      /// Show the balance of a DAO
 311      Balance {
 312          /// Name identifier for the DAO
 313          name: String,
 314      },
 315  
 316      /// Mint an imported DAO on-chain
 317      Mint {
 318          /// Name identifier for the DAO
 319          name: String,
 320      },
 321  
 322      /// Create a transfer proposal for a DAO
 323      ProposeTransfer {
 324          /// Name identifier for the DAO
 325          name: String,
 326  
 327          /// Duration of the proposal, in block windows
 328          duration: u64,
 329  
 330          /// Amount to send
 331          amount: String,
 332  
 333          /// Token ID to send
 334          token: String,
 335  
 336          /// Recipient address
 337          recipient: String,
 338  
 339          /// Optional contract spend hook to use
 340          spend_hook: Option<String>,
 341  
 342          /// Optional user data to use
 343          user_data: Option<String>,
 344      },
 345  
 346      /// Create a generic proposal for a DAO
 347      ProposeGeneric {
 348          /// Name identifier for the DAO
 349          name: String,
 350  
 351          /// Duration of the proposal, in block windows
 352          duration: u64,
 353  
 354          /// Optional user data to use
 355          user_data: Option<String>,
 356      },
 357  
 358      /// List DAO proposals
 359      Proposals {
 360          /// Name identifier for the DAO
 361          name: String,
 362      },
 363  
 364      /// View a DAO proposal data
 365      Proposal {
 366          /// Bulla identifier for the proposal
 367          bulla: String,
 368  
 369          #[structopt(long)]
 370          /// Encrypt the proposal and encode it to base64
 371          export: bool,
 372  
 373          #[structopt(long)]
 374          /// Create the proposal transaction
 375          mint_proposal: bool,
 376      },
 377  
 378      /// Import a base64 encoded and encrypted proposal from stdin
 379      ProposalImport,
 380  
 381      /// Vote on a given proposal
 382      Vote {
 383          /// Bulla identifier for the proposal
 384          bulla: String,
 385  
 386          /// Vote (0 for NO, 1 for YES)
 387          vote: u8,
 388  
 389          /// Optional vote weight (amount of governance tokens)
 390          vote_weight: Option<String>,
 391      },
 392  
 393      /// Execute a DAO proposal
 394      Exec {
 395          /// Bulla identifier for the proposal
 396          bulla: String,
 397  
 398          #[structopt(long)]
 399          /// Execute the proposal early
 400          early: bool,
 401      },
 402  
 403      /// Print the DAO contract base64-encoded spend hook
 404      SpendHook,
 405  }
 406  
 407  #[derive(Clone, Debug, Deserialize, StructOpt)]
 408  enum ExplorerSubcmd {
 409      /// Fetch a blockchain transaction by hash
 410      FetchTx {
 411          /// Transaction hash
 412          tx_hash: String,
 413  
 414          #[structopt(long)]
 415          /// Encode transaction to base64
 416          encode: bool,
 417      },
 418  
 419      /// Read a transaction from stdin and simulate it
 420      SimulateTx,
 421  
 422      /// Fetch broadcasted transactions history
 423      TxsHistory {
 424          /// Fetch specific history record (optional)
 425          tx_hash: Option<String>,
 426  
 427          #[structopt(long)]
 428          /// Encode specific history record transaction to base64
 429          encode: bool,
 430      },
 431  
 432      /// Remove reverted transactions from history
 433      ClearReverted,
 434  
 435      /// Fetch scanned blocks records
 436      ScannedBlocks {
 437          /// Fetch specific height record (optional)
 438          height: Option<u32>,
 439      },
 440  }
 441  
 442  #[derive(Clone, Debug, Deserialize, StructOpt)]
 443  enum AliasSubcmd {
 444      /// Create a Token alias
 445      Add {
 446          /// Token alias
 447          alias: String,
 448  
 449          /// Token to create alias for
 450          token: String,
 451      },
 452  
 453      /// Print alias info of optional arguments.
 454      /// If no argument is provided, list all the aliases in the wallet.
 455      Show {
 456          /// Token alias to search for
 457          #[structopt(short, long)]
 458          alias: Option<String>,
 459  
 460          /// Token to search alias for
 461          #[structopt(short, long)]
 462          token: Option<String>,
 463      },
 464  
 465      /// Remove a Token alias
 466      Remove {
 467          /// Token alias to remove
 468          alias: String,
 469      },
 470  }
 471  
 472  #[derive(Clone, Debug, Deserialize, StructOpt)]
 473  enum TokenSubcmd {
 474      /// Import a mint authority
 475      Import {
 476          /// Mint authority secret key
 477          secret_key: String,
 478  
 479          /// Mint authority token blind
 480          token_blind: String,
 481      },
 482  
 483      /// Generate a new mint authority
 484      GenerateMint,
 485  
 486      /// List token IDs with available mint authorities
 487      List,
 488  
 489      /// Mint tokens
 490      Mint {
 491          /// Token ID to mint
 492          token: String,
 493  
 494          /// Amount to mint
 495          amount: String,
 496  
 497          /// Recipient of the minted tokens
 498          recipient: String,
 499  
 500          /// Optional contract spend hook to use
 501          spend_hook: Option<String>,
 502  
 503          /// Optional user data to use
 504          user_data: Option<String>,
 505      },
 506  
 507      /// Freeze a token mint
 508      Freeze {
 509          /// Token ID to freeze
 510          token: String,
 511      },
 512  }
 513  
 514  #[derive(Clone, Debug, Deserialize, StructOpt)]
 515  enum ContractSubcmd {
 516      /// Generate a new deploy authority
 517      GenerateDeploy,
 518  
 519      /// List deploy authorities in the wallet (or a specific one)
 520      List {
 521          /// Contract ID (optional)
 522          contract_id: Option<String>,
 523      },
 524  
 525      /// Export a contract history record wasm bincode and deployment instruction, encoded to base64
 526      ExportData {
 527          /// Record transaction hash
 528          tx_hash: String,
 529      },
 530  
 531      /// Deploy a smart contract
 532      Deploy {
 533          /// Contract ID (deploy authority)
 534          deploy_auth: String,
 535  
 536          /// Path to contract wasm bincode
 537          wasm_path: String,
 538  
 539          /// Optional path to serialized deploy instruction
 540          deploy_ix: Option<String>,
 541      },
 542  
 543      /// Lock a smart contract
 544      Lock {
 545          /// Contract ID (deploy authority)
 546          deploy_auth: String,
 547      },
 548  }
 549  
 550  /// Defines a blockchain network configuration.
 551  /// Default values correspond to a local network.
 552  #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
 553  #[structopt()]
 554  struct BlockchainNetwork {
 555      #[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/cache")]
 556      /// Path to blockchain cache database
 557      cache_path: String,
 558  
 559      #[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/wallet.db")]
 560      /// Path to wallet database
 561      wallet_path: String,
 562  
 563      #[structopt(long, default_value = "changeme")]
 564      /// Password for the wallet database
 565      wallet_pass: String,
 566  
 567      #[structopt(short, long, default_value = "tcp://127.0.0.1:8240")]
 568      /// darkfid JSON-RPC endpoint
 569      endpoint: Url,
 570  
 571      #[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/history.txt")]
 572      /// Path to interactive shell history file
 573      history_path: String,
 574  }
 575  
 576  /// Auxiliary function to parse darkfid configuration file and extract requested
 577  /// blockchain network config.
 578  async fn parse_blockchain_config(
 579      config: Option<String>,
 580      network: &str,
 581  ) -> Result<BlockchainNetwork> {
 582      // Grab config path
 583      let config_path = get_config_path(config, CONFIG_FILE)?;
 584  
 585      // Parse TOML file contents
 586      let contents = read_to_string(&config_path).await?;
 587      let contents: toml::Value = match toml::from_str(&contents) {
 588          Ok(v) => v,
 589          Err(e) => {
 590              eprintln!("Failed parsing TOML config: {e}");
 591              return Err(Error::ParseFailed("Failed parsing TOML config"))
 592          }
 593      };
 594  
 595      // Grab requested network config
 596      let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
 597      let Some(network_configs) = table.get("network_config") else {
 598          return Err(Error::ParseFailed("TOML does not contain network configurations"))
 599      };
 600      let Some(network_configs) = network_configs.as_table() else {
 601          return Err(Error::ParseFailed("`network_config` not a map"))
 602      };
 603      let Some(network_config) = network_configs.get(network) else {
 604          return Err(Error::ParseFailed("TOML does not contain requested network configuration"))
 605      };
 606      let network_config = toml::to_string(&network_config).unwrap();
 607      let network_config =
 608          match BlockchainNetwork::from_iter_with_toml::<Vec<String>>(&network_config, vec![]) {
 609              Ok(v) => v,
 610              Err(e) => {
 611                  eprintln!("Failed parsing requested network configuration: {e}");
 612                  return Err(Error::ParseFailed("Failed parsing requested network configuration"))
 613              }
 614          };
 615  
 616      Ok(network_config)
 617  }
 618  
 619  /// Auxiliary function to create a `Drk` wallet for provided configuration.
 620  async fn new_wallet(
 621      cache_path: String,
 622      wallet_path: String,
 623      wallet_pass: String,
 624      endpoint: Option<Url>,
 625      ex: &ExecutorPtr,
 626      fun: bool,
 627  ) -> Drk {
 628      // Script kiddies protection
 629      if wallet_pass == "changeme" {
 630          eprintln!("Please don't use default wallet password...");
 631          exit(2);
 632      }
 633  
 634      match Drk::new(cache_path, wallet_path, wallet_pass, endpoint, ex, fun).await {
 635          Ok(wallet) => wallet,
 636          Err(e) => {
 637              eprintln!("Error initializing wallet: {e}");
 638              exit(2);
 639          }
 640      }
 641  }
 642  
 643  async_daemonize!(realmain);
 644  async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 645      // Grab blockchain network configuration
 646      let blockchain_config = match args.network.as_str() {
 647          "localnet" => parse_blockchain_config(args.config, "localnet").await?,
 648          "testnet" => parse_blockchain_config(args.config, "testnet").await?,
 649          "mainnet" => parse_blockchain_config(args.config, "mainnet").await?,
 650          _ => {
 651              eprintln!("Unsupported chain `{}`", args.network);
 652              return Err(Error::UnsupportedChain)
 653          }
 654      };
 655  
 656      match args.command {
 657          Subcmd::Interactive => {
 658              let drk = new_wallet(
 659                  blockchain_config.cache_path,
 660                  blockchain_config.wallet_path,
 661                  blockchain_config.wallet_pass,
 662                  Some(blockchain_config.endpoint.clone()),
 663                  &ex,
 664                  args.fun,
 665              )
 666              .await
 667              .into_ptr();
 668              interactive(&drk, &blockchain_config.endpoint, &blockchain_config.history_path, &ex)
 669                  .await;
 670              drk.read().await.stop_rpc_client().await?;
 671              Ok(())
 672          }
 673  
 674          Subcmd::Kaching => {
 675              if !args.fun {
 676                  println!("Apparently you don't like fun...");
 677                  return Ok(())
 678              }
 679              kaching().await;
 680              Ok(())
 681          }
 682  
 683          Subcmd::Ping => {
 684              let drk = new_wallet(
 685                  blockchain_config.cache_path,
 686                  blockchain_config.wallet_path,
 687                  blockchain_config.wallet_pass,
 688                  Some(blockchain_config.endpoint),
 689                  &ex,
 690                  args.fun,
 691              )
 692              .await;
 693              let mut output = vec![];
 694              if let Err(e) = drk.ping(&mut output).await {
 695                  print_output(&output);
 696                  return Err(e)
 697              };
 698              print_output(&output);
 699              drk.stop_rpc_client().await
 700          }
 701  
 702          Subcmd::Completions { shell } => {
 703              println!("{}", generate_completions(&shell)?);
 704              Ok(())
 705          }
 706  
 707          Subcmd::Wallet { command } => {
 708              let drk = new_wallet(
 709                  blockchain_config.cache_path,
 710                  blockchain_config.wallet_path,
 711                  blockchain_config.wallet_pass,
 712                  None,
 713                  &ex,
 714                  args.fun,
 715              )
 716              .await;
 717  
 718              match command {
 719                  WalletSubcmd::Initialize => {
 720                      if let Err(e) = drk.initialize_wallet().await {
 721                          eprintln!("Error initializing wallet: {e}");
 722                          exit(2);
 723                      }
 724                      let mut output = vec![];
 725                      if let Err(e) = drk.initialize_money(&mut output).await {
 726                          print_output(&output);
 727                          eprintln!("Failed to initialize Money: {e}");
 728                          exit(2);
 729                      }
 730                      print_output(&output);
 731                      if let Err(e) = drk.initialize_dao().await {
 732                          eprintln!("Failed to initialize DAO: {e}");
 733                          exit(2);
 734                      }
 735                      if let Err(e) = drk.initialize_deployooor() {
 736                          eprintln!("Failed to initialize Deployooor: {e}");
 737                          exit(2);
 738                      }
 739                  }
 740  
 741                  WalletSubcmd::Keygen => {
 742                      let mut output = vec![];
 743                      if let Err(e) = drk.money_keygen(&mut output).await {
 744                          print_output(&output);
 745                          eprintln!("Failed to generate keypair: {e}");
 746                          exit(2);
 747                      }
 748                      print_output(&output);
 749                  }
 750  
 751                  WalletSubcmd::Balance => {
 752                      let balmap = drk.money_balance().await?;
 753  
 754                      let aliases_map = drk.get_aliases_mapped_by_token().await?;
 755  
 756                      // Create a prettytable with the new data:
 757                      let mut table = Table::new();
 758                      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
 759                      table.set_titles(row!["Token ID", "Aliases", "Balance"]);
 760                      for (token_id, balance) in balmap.iter() {
 761                          let aliases = match aliases_map.get(token_id) {
 762                              Some(a) => a,
 763                              None => "-",
 764                          };
 765  
 766                          table.add_row(row![
 767                              token_id,
 768                              aliases,
 769                              encode_base10(*balance, BALANCE_BASE10_DECIMALS)
 770                          ]);
 771                      }
 772  
 773                      if table.is_empty() {
 774                          println!("No unspent balances found");
 775                      } else {
 776                          println!("{table}");
 777                      }
 778                  }
 779  
 780                  WalletSubcmd::Address => match drk.default_address().await {
 781                      Ok(address) => println!("{address}"),
 782                      Err(e) => {
 783                          eprintln!("Failed to fetch default address: {e}");
 784                          exit(2);
 785                      }
 786                  },
 787  
 788                  WalletSubcmd::Addresses => {
 789                      let addresses = drk.addresses().await?;
 790  
 791                      // Create a prettytable with the new data:
 792                      let mut table = Table::new();
 793                      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
 794                      table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
 795                      for (key_id, public_key, secret_key, is_default) in addresses {
 796                          let is_default = match is_default {
 797                              1 => "*",
 798                              _ => "",
 799                          };
 800                          table.add_row(row![key_id, public_key, secret_key, is_default]);
 801                      }
 802  
 803                      if table.is_empty() {
 804                          println!("No addresses found");
 805                      } else {
 806                          println!("{table}");
 807                      }
 808                  }
 809  
 810                  WalletSubcmd::DefaultAddress { index } => {
 811                      if let Err(e) = drk.set_default_address(index) {
 812                          eprintln!("Failed to set default address: {e}");
 813                          exit(2);
 814                      }
 815                  }
 816  
 817                  WalletSubcmd::Secrets => {
 818                      for secret in drk.get_money_secrets().await? {
 819                          println!("{secret}");
 820                      }
 821                  }
 822  
 823                  WalletSubcmd::ImportSecrets => {
 824                      let mut secrets = vec![];
 825                      let lines = stdin().lines();
 826                      for (i, line) in lines.enumerate() {
 827                          if let Ok(line) = line {
 828                              let bytes = bs58::decode(&line.trim()).into_vec()?;
 829                              let Ok(secret) = deserialize_async(&bytes).await else {
 830                                  println!("Warning: Failed to deserialize secret on line {i}");
 831                                  continue
 832                              };
 833                              secrets.push(secret);
 834                          }
 835                      }
 836  
 837                      let mut output = vec![];
 838                      let pubkeys = match drk.import_money_secrets(secrets, &mut output).await {
 839                          Ok(p) => {
 840                              print_output(&output);
 841                              p
 842                          }
 843                          Err(e) => {
 844                              print_output(&output);
 845                              eprintln!("Failed to import secret keys into wallet: {e}");
 846                              exit(2);
 847                          }
 848                      };
 849  
 850                      for key in pubkeys {
 851                          println!("{key}");
 852                      }
 853                  }
 854  
 855                  WalletSubcmd::Tree => {
 856                      println!("{:#?}", drk.get_money_tree().await?);
 857                  }
 858  
 859                  WalletSubcmd::Coins => {
 860                      let coins = drk.get_coins(true).await?;
 861  
 862                      if coins.is_empty() {
 863                          return Ok(())
 864                      }
 865  
 866                      let aliases_map = drk.get_aliases_mapped_by_token().await?;
 867  
 868                      let mut table = Table::new();
 869                      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
 870                      table.set_titles(row![
 871                          "Coin",
 872                          "Token ID",
 873                          "Aliases",
 874                          "Value",
 875                          "Spend Hook",
 876                          "User Data",
 877                          "Creation Height",
 878                          "Spent",
 879                          "Spent Height",
 880                          "Spent TX"
 881                      ]);
 882                      for coin in coins {
 883                          let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
 884                              Some(a) => a,
 885                              None => "-",
 886                          };
 887  
 888                          let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
 889                              format!("{}", coin.0.note.spend_hook)
 890                          } else {
 891                              String::from("-")
 892                          };
 893  
 894                          let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
 895                              bs58::encode(&serialize_async(&coin.0.note.user_data).await)
 896                                  .into_string()
 897                                  .to_string()
 898                          } else {
 899                              String::from("-")
 900                          };
 901  
 902                          let spent_height = match coin.3 {
 903                              Some(spent_height) => spent_height.to_string(),
 904                              None => String::from("-"),
 905                          };
 906  
 907                          table.add_row(row![
 908                              bs58::encode(&serialize_async(&coin.0.coin.inner()).await)
 909                                  .into_string()
 910                                  .to_string(),
 911                              coin.0.note.token_id,
 912                              aliases,
 913                              format!(
 914                                  "{} ({})",
 915                                  coin.0.note.value,
 916                                  encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
 917                              ),
 918                              spend_hook,
 919                              user_data,
 920                              coin.1,
 921                              coin.2,
 922                              spent_height,
 923                              coin.4,
 924                          ]);
 925                      }
 926  
 927                      println!("{table}");
 928                  }
 929              }
 930  
 931              Ok(())
 932          }
 933  
 934          Subcmd::Spend => {
 935              let tx = parse_tx_from_stdin().await?;
 936  
 937              let drk = new_wallet(
 938                  blockchain_config.cache_path,
 939                  blockchain_config.wallet_path,
 940                  blockchain_config.wallet_pass,
 941                  None,
 942                  &ex,
 943                  args.fun,
 944              )
 945              .await;
 946  
 947              let mut output = vec![];
 948              if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await {
 949                  print_output(&output);
 950                  eprintln!("Failed to mark transaction coins as spent: {e}");
 951                  exit(2);
 952              };
 953              print_output(&output);
 954  
 955              Ok(())
 956          }
 957  
 958          Subcmd::Unspend { coin } => {
 959              let bytes: [u8; 32] = match bs58::decode(&coin).into_vec()?.try_into() {
 960                  Ok(b) => b,
 961                  Err(e) => {
 962                      eprintln!("Invalid coin: {e:?}");
 963                      exit(2);
 964                  }
 965              };
 966  
 967              let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
 968                  Some(v) => v,
 969                  None => {
 970                      eprintln!("Invalid coin");
 971                      exit(2);
 972                  }
 973              };
 974  
 975              let coin = Coin::from(elem);
 976              let drk = new_wallet(
 977                  blockchain_config.cache_path,
 978                  blockchain_config.wallet_path,
 979                  blockchain_config.wallet_pass,
 980                  None,
 981                  &ex,
 982                  args.fun,
 983              )
 984              .await;
 985              if let Err(e) = drk.unspend_coin(&coin).await {
 986                  eprintln!("Failed to mark coin as unspent: {e}");
 987                  exit(2);
 988              }
 989  
 990              Ok(())
 991          }
 992  
 993          Subcmd::Transfer { amount, token, recipient, spend_hook, user_data, half_split } => {
 994              let drk = new_wallet(
 995                  blockchain_config.cache_path,
 996                  blockchain_config.wallet_path,
 997                  blockchain_config.wallet_pass,
 998                  Some(blockchain_config.endpoint),
 999                  &ex,
1000                  args.fun,
1001              )
1002              .await;
1003  
1004              if let Err(e) = f64::from_str(&amount) {
1005                  eprintln!("Invalid amount: {e}");
1006                  exit(2);
1007              }
1008  
1009              let rcpt = match PublicKey::from_str(&recipient) {
1010                  Ok(r) => r,
1011                  Err(e) => {
1012                      eprintln!("Invalid recipient: {e}");
1013                      exit(2);
1014                  }
1015              };
1016  
1017              let token_id = match drk.get_token(token).await {
1018                  Ok(t) => t,
1019                  Err(e) => {
1020                      eprintln!("Invalid token alias: {e}");
1021                      exit(2);
1022                  }
1023              };
1024  
1025              let spend_hook = match spend_hook {
1026                  Some(s) => match FuncId::from_str(&s) {
1027                      Ok(s) => Some(s),
1028                      Err(e) => {
1029                          eprintln!("Invalid spend hook: {e}");
1030                          exit(2);
1031                      }
1032                  },
1033                  None => None,
1034              };
1035  
1036              let user_data = match user_data {
1037                  Some(u) => {
1038                      let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
1039                          Ok(b) => b,
1040                          Err(e) => {
1041                              eprintln!("Invalid user data: {e:?}");
1042                              exit(2);
1043                          }
1044                      };
1045  
1046                      match pallas::Base::from_repr(bytes).into() {
1047                          Some(v) => Some(v),
1048                          None => {
1049                              eprintln!("Invalid user data");
1050                              exit(2);
1051                          }
1052                      }
1053                  }
1054                  None => None,
1055              };
1056  
1057              let tx = match drk
1058                  .transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split)
1059                  .await
1060              {
1061                  Ok(t) => t,
1062                  Err(e) => {
1063                      eprintln!("Failed to create payment transaction: {e}");
1064                      exit(2);
1065                  }
1066              };
1067  
1068              println!("{}", base64::encode(&serialize_async(&tx).await));
1069  
1070              drk.stop_rpc_client().await
1071          }
1072  
1073          Subcmd::Otc { command } => match command {
1074              OtcSubcmd::Init { value_pair, token_pair } => {
1075                  let drk = new_wallet(
1076                      blockchain_config.cache_path,
1077                      blockchain_config.wallet_path,
1078                      blockchain_config.wallet_pass,
1079                      Some(blockchain_config.endpoint),
1080                      &ex,
1081                      args.fun,
1082                  )
1083                  .await;
1084                  let value_pair = parse_value_pair(&value_pair)?;
1085                  let token_pair = parse_token_pair(&drk, &token_pair).await?;
1086  
1087                  let half = match drk.init_swap(value_pair, token_pair, None, None, None).await {
1088                      Ok(h) => h,
1089                      Err(e) => {
1090                          eprintln!("Failed to create swap transaction half: {e}");
1091                          exit(2);
1092                      }
1093                  };
1094  
1095                  println!("{}", base64::encode(&serialize_async(&half).await));
1096                  drk.stop_rpc_client().await
1097              }
1098  
1099              OtcSubcmd::Join => {
1100                  let mut buf = String::new();
1101                  stdin().read_to_string(&mut buf)?;
1102                  let Some(bytes) = base64::decode(buf.trim()) else {
1103                      eprintln!("Failed to decode partial swap data");
1104                      exit(2);
1105                  };
1106  
1107                  let partial: PartialSwapData = deserialize_async(&bytes).await?;
1108  
1109                  let drk = new_wallet(
1110                      blockchain_config.cache_path,
1111                      blockchain_config.wallet_path,
1112                      blockchain_config.wallet_pass,
1113                      Some(blockchain_config.endpoint),
1114                      &ex,
1115                      args.fun,
1116                  )
1117                  .await;
1118                  let tx = match drk.join_swap(partial, None, None, None).await {
1119                      Ok(tx) => tx,
1120                      Err(e) => {
1121                          eprintln!("Failed to create a join swap transaction: {e}");
1122                          exit(2);
1123                      }
1124                  };
1125  
1126                  println!("{}", base64::encode(&serialize_async(&tx).await));
1127                  drk.stop_rpc_client().await
1128              }
1129  
1130              OtcSubcmd::Inspect => {
1131                  let mut buf = String::new();
1132                  stdin().read_to_string(&mut buf)?;
1133                  let Some(bytes) = base64::decode(buf.trim()) else {
1134                      eprintln!("Failed to decode swap transaction");
1135                      exit(2);
1136                  };
1137  
1138                  let drk = new_wallet(
1139                      blockchain_config.cache_path,
1140                      blockchain_config.wallet_path,
1141                      blockchain_config.wallet_pass,
1142                      None,
1143                      &ex,
1144                      args.fun,
1145                  )
1146                  .await;
1147                  let mut output = vec![];
1148                  if let Err(e) = drk.inspect_swap(bytes, &mut output).await {
1149                      print_output(&output);
1150                      eprintln!("Failed to inspect swap: {e}");
1151                      exit(2);
1152                  };
1153                  print_output(&output);
1154  
1155                  Ok(())
1156              }
1157  
1158              OtcSubcmd::Sign => {
1159                  let mut tx = parse_tx_from_stdin().await?;
1160  
1161                  let drk = new_wallet(
1162                      blockchain_config.cache_path,
1163                      blockchain_config.wallet_path,
1164                      blockchain_config.wallet_pass,
1165                      None,
1166                      &ex,
1167                      args.fun,
1168                  )
1169                  .await;
1170                  if let Err(e) = drk.sign_swap(&mut tx).await {
1171                      eprintln!("Failed to sign joined swap transaction: {e}");
1172                      exit(2);
1173                  };
1174  
1175                  println!("{}", base64::encode(&serialize_async(&tx).await));
1176                  Ok(())
1177              }
1178          },
1179  
1180          Subcmd::Dao { command } => match command {
1181              DaoSubcmd::Create {
1182                  proposer_limit,
1183                  quorum,
1184                  early_exec_quorum,
1185                  approval_ratio,
1186                  gov_token_id,
1187              } => {
1188                  if let Err(e) = f64::from_str(&proposer_limit) {
1189                      eprintln!("Invalid proposer limit: {e}");
1190                      exit(2);
1191                  }
1192                  if let Err(e) = f64::from_str(&quorum) {
1193                      eprintln!("Invalid quorum: {e}");
1194                      exit(2);
1195                  }
1196                  if let Err(e) = f64::from_str(&early_exec_quorum) {
1197                      eprintln!("Invalid early exec quorum: {e}");
1198                      exit(2);
1199                  }
1200  
1201                  let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
1202                  let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
1203                  let early_exec_quorum =
1204                      decode_base10(&early_exec_quorum, BALANCE_BASE10_DECIMALS, true)?;
1205  
1206                  if approval_ratio > 1.0 {
1207                      eprintln!("Error: Approval ratio cannot be >1.0");
1208                      exit(2);
1209                  }
1210  
1211                  let approval_ratio_base = 100_u64;
1212                  let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
1213  
1214                  let drk = new_wallet(
1215                      blockchain_config.cache_path,
1216                      blockchain_config.wallet_path,
1217                      blockchain_config.wallet_pass,
1218                      None,
1219                      &ex,
1220                      args.fun,
1221                  )
1222                  .await;
1223                  let gov_token_id = match drk.get_token(gov_token_id).await {
1224                      Ok(g) => g,
1225                      Err(e) => {
1226                          eprintln!("Invalid Token ID: {e}");
1227                          exit(2);
1228                      }
1229                  };
1230  
1231                  let notes_keypair = Keypair::random(&mut OsRng);
1232                  let proposer_keypair = Keypair::random(&mut OsRng);
1233                  let proposals_keypair = Keypair::random(&mut OsRng);
1234                  let votes_keypair = Keypair::random(&mut OsRng);
1235                  let exec_keypair = Keypair::random(&mut OsRng);
1236                  let early_exec_keypair = Keypair::random(&mut OsRng);
1237                  let bulla_blind = BaseBlind::random(&mut OsRng);
1238  
1239                  let params = DaoParams::new(
1240                      proposer_limit,
1241                      quorum,
1242                      early_exec_quorum,
1243                      approval_ratio_base,
1244                      approval_ratio_quot,
1245                      gov_token_id,
1246                      Some(notes_keypair.secret),
1247                      notes_keypair.public,
1248                      Some(proposer_keypair.secret),
1249                      proposer_keypair.public,
1250                      Some(proposals_keypair.secret),
1251                      proposals_keypair.public,
1252                      Some(votes_keypair.secret),
1253                      votes_keypair.public,
1254                      Some(exec_keypair.secret),
1255                      exec_keypair.public,
1256                      Some(early_exec_keypair.secret),
1257                      early_exec_keypair.public,
1258                      bulla_blind,
1259                  );
1260  
1261                  println!("{}", params.toml_str());
1262  
1263                  Ok(())
1264              }
1265  
1266              DaoSubcmd::View => {
1267                  let mut buf = String::new();
1268                  stdin().read_to_string(&mut buf)?;
1269                  let params = DaoParams::from_toml_str(&buf)?;
1270                  println!("{params}");
1271  
1272                  Ok(())
1273              }
1274  
1275              DaoSubcmd::Import { name } => {
1276                  let mut buf = String::new();
1277                  stdin().read_to_string(&mut buf)?;
1278                  let params = DaoParams::from_toml_str(&buf)?;
1279  
1280                  let drk = new_wallet(
1281                      blockchain_config.cache_path,
1282                      blockchain_config.wallet_path,
1283                      blockchain_config.wallet_pass,
1284                      None,
1285                      &ex,
1286                      args.fun,
1287                  )
1288                  .await;
1289                  let mut output = vec![];
1290                  if let Err(e) = drk.import_dao(&name, &params, &mut output).await {
1291                      print_output(&output);
1292                      eprintln!("Failed to import DAO: {e}");
1293                      exit(2);
1294                  }
1295                  print_output(&output);
1296  
1297                  Ok(())
1298              }
1299  
1300              DaoSubcmd::List { name } => {
1301                  let drk = new_wallet(
1302                      blockchain_config.cache_path,
1303                      blockchain_config.wallet_path,
1304                      blockchain_config.wallet_pass,
1305                      None,
1306                      &ex,
1307                      args.fun,
1308                  )
1309                  .await;
1310                  let mut output = vec![];
1311                  if let Err(e) = drk.dao_list(&name, &mut output).await {
1312                      print_output(&output);
1313                      eprintln!("Failed to list DAO: {e}");
1314                      exit(2);
1315                  }
1316                  print_output(&output);
1317  
1318                  Ok(())
1319              }
1320  
1321              DaoSubcmd::Balance { name } => {
1322                  let drk = new_wallet(
1323                      blockchain_config.cache_path,
1324                      blockchain_config.wallet_path,
1325                      blockchain_config.wallet_pass,
1326                      None,
1327                      &ex,
1328                      args.fun,
1329                  )
1330                  .await;
1331                  let balmap = match drk.dao_balance(&name).await {
1332                      Ok(b) => b,
1333                      Err(e) => {
1334                          eprintln!("Failed to fetch DAO balance: {e}");
1335                          exit(2);
1336                      }
1337                  };
1338  
1339                  let aliases_map = match drk.get_aliases_mapped_by_token().await {
1340                      Ok(a) => a,
1341                      Err(e) => {
1342                          eprintln!("Failed to fetch wallet aliases: {e}");
1343                          exit(2);
1344                      }
1345                  };
1346  
1347                  // Create a prettytable with the new data:
1348                  let mut table = Table::new();
1349                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
1350                  table.set_titles(row!["Token ID", "Aliases", "Balance"]);
1351                  for (token_id, balance) in balmap.iter() {
1352                      let aliases = match aliases_map.get(token_id) {
1353                          Some(a) => a,
1354                          None => "-",
1355                      };
1356  
1357                      table.add_row(row![
1358                          token_id,
1359                          aliases,
1360                          encode_base10(*balance, BALANCE_BASE10_DECIMALS)
1361                      ]);
1362                  }
1363  
1364                  if table.is_empty() {
1365                      println!("No unspent balances found");
1366                  } else {
1367                      println!("{table}");
1368                  }
1369  
1370                  Ok(())
1371              }
1372  
1373              DaoSubcmd::Mint { name } => {
1374                  let drk = new_wallet(
1375                      blockchain_config.cache_path,
1376                      blockchain_config.wallet_path,
1377                      blockchain_config.wallet_pass,
1378                      Some(blockchain_config.endpoint),
1379                      &ex,
1380                      args.fun,
1381                  )
1382                  .await;
1383                  let tx = match drk.dao_mint(&name).await {
1384                      Ok(tx) => tx,
1385                      Err(e) => {
1386                          eprintln!("Failed to mint DAO: {e}");
1387                          exit(2);
1388                      }
1389                  };
1390  
1391                  println!("{}", base64::encode(&serialize_async(&tx).await));
1392                  drk.stop_rpc_client().await
1393              }
1394  
1395              DaoSubcmd::ProposeTransfer {
1396                  name,
1397                  duration,
1398                  amount,
1399                  token,
1400                  recipient,
1401                  spend_hook,
1402                  user_data,
1403              } => {
1404                  let drk = new_wallet(
1405                      blockchain_config.cache_path,
1406                      blockchain_config.wallet_path,
1407                      blockchain_config.wallet_pass,
1408                      Some(blockchain_config.endpoint),
1409                      &ex,
1410                      args.fun,
1411                  )
1412                  .await;
1413  
1414                  if let Err(e) = f64::from_str(&amount) {
1415                      eprintln!("Invalid amount: {e}");
1416                      exit(2);
1417                  }
1418  
1419                  let rcpt = match PublicKey::from_str(&recipient) {
1420                      Ok(r) => r,
1421                      Err(e) => {
1422                          eprintln!("Invalid recipient: {e}");
1423                          exit(2);
1424                      }
1425                  };
1426  
1427                  let token_id = match drk.get_token(token).await {
1428                      Ok(t) => t,
1429                      Err(e) => {
1430                          eprintln!("Invalid token alias: {e}");
1431                          exit(2);
1432                      }
1433                  };
1434  
1435                  let spend_hook = match spend_hook {
1436                      Some(s) => match FuncId::from_str(&s) {
1437                          Ok(s) => Some(s),
1438                          Err(e) => {
1439                              eprintln!("Invalid spend hook: {e}");
1440                              exit(2);
1441                          }
1442                      },
1443                      None => None,
1444                  };
1445  
1446                  let user_data = match user_data {
1447                      Some(u) => {
1448                          let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
1449                              Ok(b) => b,
1450                              Err(e) => {
1451                                  eprintln!("Invalid user data: {e:?}");
1452                                  exit(2);
1453                              }
1454                          };
1455  
1456                          match pallas::Base::from_repr(bytes).into() {
1457                              Some(v) => Some(v),
1458                              None => {
1459                                  eprintln!("Invalid user data");
1460                                  exit(2);
1461                              }
1462                          }
1463                      }
1464                      None => None,
1465                  };
1466  
1467                  let proposal = match drk
1468                      .dao_propose_transfer(
1469                          &name, duration, &amount, token_id, rcpt, spend_hook, user_data,
1470                      )
1471                      .await
1472                  {
1473                      Ok(p) => p,
1474                      Err(e) => {
1475                          eprintln!("Failed to create DAO transfer proposal: {e}");
1476                          exit(2);
1477                      }
1478                  };
1479  
1480                  println!("Generated proposal: {}", proposal.bulla());
1481  
1482                  drk.stop_rpc_client().await
1483              }
1484  
1485              DaoSubcmd::ProposeGeneric { name, duration, user_data } => {
1486                  let drk = new_wallet(
1487                      blockchain_config.cache_path,
1488                      blockchain_config.wallet_path,
1489                      blockchain_config.wallet_pass,
1490                      Some(blockchain_config.endpoint),
1491                      &ex,
1492                      args.fun,
1493                  )
1494                  .await;
1495  
1496                  let user_data = match user_data {
1497                      Some(u) => {
1498                          let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
1499                              Ok(b) => b,
1500                              Err(e) => {
1501                                  eprintln!("Invalid user data: {e:?}");
1502                                  exit(2);
1503                              }
1504                          };
1505  
1506                          match pallas::Base::from_repr(bytes).into() {
1507                              Some(v) => Some(v),
1508                              None => {
1509                                  eprintln!("Invalid user data");
1510                                  exit(2);
1511                              }
1512                          }
1513                      }
1514                      None => None,
1515                  };
1516  
1517                  let proposal = match drk.dao_propose_generic(&name, duration, user_data).await {
1518                      Ok(p) => p,
1519                      Err(e) => {
1520                          eprintln!("Failed to create DAO generic proposal: {e}");
1521                          exit(2);
1522                      }
1523                  };
1524  
1525                  println!("Generated proposal: {}", proposal.bulla());
1526  
1527                  drk.stop_rpc_client().await
1528              }
1529  
1530              DaoSubcmd::Proposals { name } => {
1531                  let drk = new_wallet(
1532                      blockchain_config.cache_path,
1533                      blockchain_config.wallet_path,
1534                      blockchain_config.wallet_pass,
1535                      None,
1536                      &ex,
1537                      args.fun,
1538                  )
1539                  .await;
1540                  let proposals = drk.get_dao_proposals(&name).await?;
1541  
1542                  for (i, proposal) in proposals.iter().enumerate() {
1543                      println!("{i}. {}", proposal.bulla());
1544                  }
1545  
1546                  Ok(())
1547              }
1548  
1549              DaoSubcmd::Proposal { bulla, export, mint_proposal } => {
1550                  let bulla = match DaoProposalBulla::from_str(&bulla) {
1551                      Ok(b) => b,
1552                      Err(e) => {
1553                          eprintln!("Invalid proposal bulla: {e}");
1554                          exit(2);
1555                      }
1556                  };
1557  
1558                  let drk = new_wallet(
1559                      blockchain_config.cache_path,
1560                      blockchain_config.wallet_path,
1561                      blockchain_config.wallet_pass,
1562                      Some(blockchain_config.endpoint),
1563                      &ex,
1564                      args.fun,
1565                  )
1566                  .await;
1567                  let proposal = drk.get_dao_proposal_by_bulla(&bulla).await?;
1568  
1569                  if export {
1570                      // Retrieve the DAO
1571                      let dao = drk.get_dao_by_bulla(&proposal.proposal.dao_bulla).await?;
1572  
1573                      // Encypt the proposal
1574                      let enc_note = AeadEncryptedNote::encrypt(
1575                          &proposal,
1576                          &dao.params.dao.proposals_public_key,
1577                          &mut OsRng,
1578                      )
1579                      .unwrap();
1580  
1581                      // Export it to base64
1582                      println!("{}", base64::encode(&serialize_async(&enc_note).await));
1583                      return drk.stop_rpc_client().await
1584                  }
1585  
1586                  if mint_proposal {
1587                      // Identify proposal type by its auth calls
1588                      for call in &proposal.proposal.auth_calls {
1589                          // We only support transfer right now
1590                          if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1591                              let tx = match drk.dao_transfer_proposal_tx(&proposal).await {
1592                                  Ok(tx) => tx,
1593                                  Err(e) => {
1594                                      eprintln!("Failed to create DAO transfer proposal: {e}");
1595                                      exit(2);
1596                                  }
1597                              };
1598  
1599                              println!("{}", base64::encode(&serialize_async(&tx).await));
1600                              return drk.stop_rpc_client().await
1601                          }
1602                      }
1603  
1604                      // If proposal has no auth calls, we consider it a generic one
1605                      if proposal.proposal.auth_calls.is_empty() {
1606                          let tx = match drk.dao_generic_proposal_tx(&proposal).await {
1607                              Ok(tx) => tx,
1608                              Err(e) => {
1609                                  eprintln!("Failed to create DAO generic proposal: {e}");
1610                                  exit(2);
1611                              }
1612                          };
1613  
1614                          println!("{}", base64::encode(&serialize_async(&tx).await));
1615                          return drk.stop_rpc_client().await
1616                      }
1617  
1618                      eprintln!("Unsuported DAO proposal");
1619                      exit(2);
1620                  }
1621  
1622                  println!("{proposal}");
1623  
1624                  let mut contract_calls = "\nInvoked contracts:\n".to_string();
1625                  for call in proposal.proposal.auth_calls {
1626                      contract_calls.push_str(&format!(
1627                          "\tContract: {}\n\tFunction: {}\n\tData: ",
1628                          call.contract_id, call.function_code
1629                      ));
1630  
1631                      if call.auth_data.is_empty() {
1632                          contract_calls.push_str("-\n");
1633                          continue;
1634                      }
1635  
1636                      if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1637                          // We know that the plaintext data live in the data plaintext vec
1638                          if proposal.data.is_none() {
1639                              contract_calls.push_str("-\n");
1640                              continue;
1641                          }
1642                          let coin: CoinAttributes =
1643                              deserialize_async(proposal.data.as_ref().unwrap()).await?;
1644                          let spend_hook = if coin.spend_hook == FuncId::none() {
1645                              "-".to_string()
1646                          } else {
1647                              format!("{}", coin.spend_hook)
1648                          };
1649  
1650                          let user_data = if coin.user_data == pallas::Base::ZERO {
1651                              "-".to_string()
1652                          } else {
1653                              format!("{:?}", coin.user_data)
1654                          };
1655  
1656                          contract_calls.push_str(&format!("\n\t\t{}: {}\n\t\t{}: {} ({})\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\n",
1657                          "Recipient",
1658                          coin.public_key,
1659                          "Amount",
1660                          coin.value,
1661                          encode_base10(coin.value, BALANCE_BASE10_DECIMALS),
1662                          "Token",
1663                          coin.token_id,
1664                          "Spend hook",
1665                          spend_hook,
1666                          "User data",
1667                          user_data,
1668                          "Blind",
1669                          coin.blind));
1670                      }
1671                  }
1672  
1673                  println!("{contract_calls}");
1674  
1675                  let votes = drk.get_dao_proposal_votes(&bulla).await?;
1676                  let mut total_yes_vote_value = 0;
1677                  let mut total_no_vote_value = 0;
1678                  let mut total_all_vote_value = 0;
1679                  let mut table = Table::new();
1680                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
1681                  table.set_titles(row!["Transaction", "Tokens", "Vote"]);
1682                  for vote in votes {
1683                      let vote_option = if vote.vote_option {
1684                          total_yes_vote_value += vote.all_vote_value;
1685                          "Yes"
1686                      } else {
1687                          total_no_vote_value += vote.all_vote_value;
1688                          "No"
1689                      };
1690                      total_all_vote_value += vote.all_vote_value;
1691  
1692                      table.add_row(row![
1693                          vote.tx_hash,
1694                          encode_base10(vote.all_vote_value, BALANCE_BASE10_DECIMALS),
1695                          vote_option
1696                      ]);
1697                  }
1698  
1699                  let outcome = if table.is_empty() {
1700                      println!("Votes: No votes found");
1701                      "Unknown"
1702                  } else {
1703                      println!("Votes:");
1704                      println!("{table}");
1705                      println!(
1706                          "Total tokens votes: {}",
1707                          encode_base10(total_all_vote_value, BALANCE_BASE10_DECIMALS)
1708                      );
1709                      let approval_ratio =
1710                          (total_yes_vote_value as f64 * 100.0) / total_all_vote_value as f64;
1711                      println!(
1712                          "Total tokens Yes votes: {} ({approval_ratio:.2}%)",
1713                          encode_base10(total_yes_vote_value, BALANCE_BASE10_DECIMALS)
1714                      );
1715                      println!(
1716                          "Total tokens No votes: {} ({:.2}%)",
1717                          encode_base10(total_no_vote_value, BALANCE_BASE10_DECIMALS),
1718                          (total_no_vote_value as f64 * 100.0) / total_all_vote_value as f64
1719                      );
1720  
1721                      let dao = drk.get_dao_by_bulla(&proposal.proposal.dao_bulla).await?;
1722                      if total_all_vote_value >= dao.params.dao.quorum &&
1723                          approval_ratio >=
1724                              (dao.params.dao.approval_ratio_quot /
1725                                  dao.params.dao.approval_ratio_base)
1726                                  as f64
1727                      {
1728                          "Approved"
1729                      } else {
1730                          "Rejected"
1731                      }
1732                  };
1733  
1734                  if let Some(exec_tx_hash) = proposal.exec_tx_hash {
1735                      println!("Proposal was executed on transaction: {exec_tx_hash}");
1736                      return drk.stop_rpc_client().await
1737                  }
1738  
1739                  // Retrieve next block height and current block time target,
1740                  // to compute their window.
1741                  let next_block_height = drk.get_next_block_height().await?;
1742                  let block_target = drk.get_block_target().await?;
1743                  let current_window = blockwindow(next_block_height, block_target);
1744                  let end_time = proposal.proposal.creation_blockwindow +
1745                      proposal.proposal.duration_blockwindows;
1746                  let (voting_status, proposal_status_message) = if current_window < end_time {
1747                      ("Ongoing", format!("Current proposal outcome: {outcome}"))
1748                  } else {
1749                      ("Concluded", format!("Proposal outcome: {outcome}"))
1750                  };
1751                  println!("Voting status: {voting_status}");
1752                  println!("{proposal_status_message}");
1753  
1754                  drk.stop_rpc_client().await
1755              }
1756  
1757              DaoSubcmd::ProposalImport => {
1758                  let mut buf = String::new();
1759                  stdin().read_to_string(&mut buf)?;
1760                  let Some(bytes) = base64::decode(buf.trim()) else {
1761                      eprintln!("Failed to decode encrypted proposal data");
1762                      exit(2);
1763                  };
1764                  let encrypted_proposal: AeadEncryptedNote = deserialize_async(&bytes).await?;
1765  
1766                  let drk = new_wallet(
1767                      blockchain_config.cache_path,
1768                      blockchain_config.wallet_path,
1769                      blockchain_config.wallet_pass,
1770                      None,
1771                      &ex,
1772                      args.fun,
1773                  )
1774                  .await;
1775  
1776                  // Retrieve all DAOs to try to decrypt the proposal
1777                  let daos = drk.get_daos().await?;
1778                  for dao in &daos {
1779                      // Check if we have the proposals key
1780                      let Some(proposals_secret_key) = dao.params.proposals_secret_key else {
1781                          continue
1782                      };
1783  
1784                      // Try to decrypt the proposal
1785                      let Ok(proposal) =
1786                          encrypted_proposal.decrypt::<ProposalRecord>(&proposals_secret_key)
1787                      else {
1788                          continue
1789                      };
1790  
1791                      let proposal = match drk.get_dao_proposal_by_bulla(&proposal.bulla()).await {
1792                          Ok(p) => {
1793                              let mut our_proposal = p;
1794                              our_proposal.data = proposal.data;
1795                              our_proposal
1796                          }
1797                          Err(_) => proposal,
1798                      };
1799  
1800                      return drk.put_dao_proposal(&proposal).await
1801                  }
1802  
1803                  eprintln!("Couldn't decrypt the proposal with out DAO keys");
1804                  exit(2);
1805              }
1806  
1807              DaoSubcmd::Vote { bulla, vote, vote_weight } => {
1808                  let bulla = match DaoProposalBulla::from_str(&bulla) {
1809                      Ok(b) => b,
1810                      Err(e) => {
1811                          eprintln!("Invalid proposal bulla: {e}");
1812                          exit(2);
1813                      }
1814                  };
1815  
1816                  if vote > 1 {
1817                      eprintln!("Vote can be either 0 (NO) or 1 (YES)");
1818                      exit(2);
1819                  }
1820                  let vote = vote != 0;
1821  
1822                  let weight = match vote_weight {
1823                      Some(w) => {
1824                          if let Err(e) = f64::from_str(&w) {
1825                              eprintln!("Invalid vote weight: {e}");
1826                              exit(2);
1827                          }
1828                          Some(decode_base10(&w, BALANCE_BASE10_DECIMALS, true)?)
1829                      }
1830                      None => None,
1831                  };
1832  
1833                  let drk = new_wallet(
1834                      blockchain_config.cache_path,
1835                      blockchain_config.wallet_path,
1836                      blockchain_config.wallet_pass,
1837                      Some(blockchain_config.endpoint),
1838                      &ex,
1839                      args.fun,
1840                  )
1841                  .await;
1842                  let tx = match drk.dao_vote(&bulla, vote, weight).await {
1843                      Ok(tx) => tx,
1844                      Err(e) => {
1845                          eprintln!("Failed to create DAO Vote transaction: {e}");
1846                          exit(2);
1847                      }
1848                  };
1849  
1850                  println!("{}", base64::encode(&serialize_async(&tx).await));
1851                  drk.stop_rpc_client().await
1852              }
1853  
1854              DaoSubcmd::Exec { bulla, early } => {
1855                  let bulla = match DaoProposalBulla::from_str(&bulla) {
1856                      Ok(b) => b,
1857                      Err(e) => {
1858                          eprintln!("Invalid proposal bulla: {e}");
1859                          exit(2);
1860                      }
1861                  };
1862  
1863                  let drk = new_wallet(
1864                      blockchain_config.cache_path,
1865                      blockchain_config.wallet_path,
1866                      blockchain_config.wallet_pass,
1867                      Some(blockchain_config.endpoint),
1868                      &ex,
1869                      args.fun,
1870                  )
1871                  .await;
1872                  let proposal = drk.get_dao_proposal_by_bulla(&bulla).await?;
1873  
1874                  // Identify proposal type by its auth calls
1875                  for call in &proposal.proposal.auth_calls {
1876                      // We only support transfer right now
1877                      if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1878                          let tx = match drk.dao_exec_transfer(&proposal, early).await {
1879                              Ok(tx) => tx,
1880                              Err(e) => {
1881                                  eprintln!("Failed to execute DAO transfer proposal: {e}");
1882                                  exit(2);
1883                              }
1884                          };
1885  
1886                          println!("{}", base64::encode(&serialize_async(&tx).await));
1887                          return drk.stop_rpc_client().await
1888                      }
1889                  }
1890  
1891                  // If proposal has no auth calls, we consider it a generic one
1892                  if proposal.proposal.auth_calls.is_empty() {
1893                      let tx = match drk.dao_exec_generic(&proposal, early).await {
1894                          Ok(tx) => tx,
1895                          Err(e) => {
1896                              eprintln!("Failed to execute DAO generic proposal: {e}");
1897                              exit(2);
1898                          }
1899                      };
1900  
1901                      println!("{}", base64::encode(&serialize_async(&tx).await));
1902                      return drk.stop_rpc_client().await
1903                  }
1904  
1905                  eprintln!("Unsuported DAO proposal");
1906                  exit(2);
1907              }
1908  
1909              DaoSubcmd::SpendHook => {
1910                  let spend_hook =
1911                      FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
1912                          .to_func_id();
1913  
1914                  println!("{spend_hook}");
1915  
1916                  Ok(())
1917              }
1918          },
1919  
1920          Subcmd::AttachFee => {
1921              let mut tx = parse_tx_from_stdin().await?;
1922  
1923              let drk = new_wallet(
1924                  blockchain_config.cache_path,
1925                  blockchain_config.wallet_path,
1926                  blockchain_config.wallet_pass,
1927                  Some(blockchain_config.endpoint),
1928                  &ex,
1929                  args.fun,
1930              )
1931              .await;
1932              if let Err(e) = drk.attach_fee(&mut tx).await {
1933                  eprintln!("Failed to attach the fee call to the transaction: {e}");
1934                  exit(2);
1935              };
1936  
1937              println!("{}", base64::encode(&serialize_async(&tx).await));
1938  
1939              drk.stop_rpc_client().await
1940          }
1941  
1942          Subcmd::Inspect => {
1943              let tx = parse_tx_from_stdin().await?;
1944  
1945              println!("{tx:#?}");
1946  
1947              Ok(())
1948          }
1949  
1950          Subcmd::Broadcast => {
1951              let tx = parse_tx_from_stdin().await?;
1952  
1953              let drk = new_wallet(
1954                  blockchain_config.cache_path,
1955                  blockchain_config.wallet_path,
1956                  blockchain_config.wallet_pass,
1957                  Some(blockchain_config.endpoint),
1958                  &ex,
1959                  args.fun,
1960              )
1961              .await;
1962  
1963              if let Err(e) = drk.simulate_tx(&tx).await {
1964                  eprintln!("Failed to simulate tx: {e}");
1965                  exit(2);
1966              };
1967  
1968              let mut output = vec![];
1969              if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await {
1970                  print_output(&output);
1971                  eprintln!("Failed to mark transaction coins as spent: {e}");
1972                  exit(2);
1973              };
1974  
1975              let txid = match drk.broadcast_tx(&tx, &mut output).await {
1976                  Ok(t) => t,
1977                  Err(e) => {
1978                      print_output(&output);
1979                      eprintln!("Failed to broadcast transaction: {e}");
1980                      exit(2);
1981                  }
1982              };
1983              print_output(&output);
1984  
1985              println!("Transaction ID: {txid}");
1986  
1987              drk.stop_rpc_client().await
1988          }
1989  
1990          Subcmd::Scan { reset } => {
1991              let drk = new_wallet(
1992                  blockchain_config.cache_path,
1993                  blockchain_config.wallet_path,
1994                  blockchain_config.wallet_pass,
1995                  Some(blockchain_config.endpoint),
1996                  &ex,
1997                  args.fun,
1998              )
1999              .await;
2000  
2001              if let Some(height) = reset {
2002                  let mut buf = vec![];
2003                  if let Err(e) = drk.reset_to_height(height, &mut buf).await {
2004                      print_output(&buf);
2005                      eprintln!("Failed during wallet reset: {e}");
2006                      exit(2);
2007                  }
2008                  print_output(&buf);
2009              }
2010  
2011              if let Err(e) = drk.scan_blocks(&mut vec![], None, &true).await {
2012                  eprintln!("Failed during scanning: {e}");
2013                  exit(2);
2014              }
2015              println!("Finished scanning blockchain");
2016  
2017              drk.stop_rpc_client().await
2018          }
2019  
2020          Subcmd::Explorer { command } => match command {
2021              ExplorerSubcmd::FetchTx { tx_hash, encode } => {
2022                  let tx_hash = TransactionHash(*blake3::Hash::from_hex(&tx_hash)?.as_bytes());
2023  
2024                  let drk = new_wallet(
2025                      blockchain_config.cache_path,
2026                      blockchain_config.wallet_path,
2027                      blockchain_config.wallet_pass,
2028                      Some(blockchain_config.endpoint),
2029                      &ex,
2030                      args.fun,
2031                  )
2032                  .await;
2033  
2034                  let tx = match drk.get_tx(&tx_hash).await {
2035                      Ok(tx) => tx,
2036                      Err(e) => {
2037                          eprintln!("Failed to fetch transaction: {e}");
2038                          exit(2);
2039                      }
2040                  };
2041  
2042                  let Some(tx) = tx else {
2043                      println!("Transaction was not found");
2044                      exit(1);
2045                  };
2046  
2047                  // Make sure the tx is correct
2048                  assert_eq!(tx.hash(), tx_hash);
2049  
2050                  if encode {
2051                      println!("{}", base64::encode(&serialize_async(&tx).await));
2052                      exit(1)
2053                  }
2054  
2055                  println!("Transaction ID: {tx_hash}");
2056                  println!("{tx:?}");
2057  
2058                  drk.stop_rpc_client().await
2059              }
2060  
2061              ExplorerSubcmd::SimulateTx => {
2062                  let tx = parse_tx_from_stdin().await?;
2063  
2064                  let drk = new_wallet(
2065                      blockchain_config.cache_path,
2066                      blockchain_config.wallet_path,
2067                      blockchain_config.wallet_pass,
2068                      Some(blockchain_config.endpoint),
2069                      &ex,
2070                      args.fun,
2071                  )
2072                  .await;
2073  
2074                  let is_valid = match drk.simulate_tx(&tx).await {
2075                      Ok(b) => b,
2076                      Err(e) => {
2077                          eprintln!("Failed to simulate tx: {e}");
2078                          exit(2);
2079                      }
2080                  };
2081  
2082                  println!("Transaction ID: {}", tx.hash());
2083                  println!("State: {}", if is_valid { "valid" } else { "invalid" });
2084  
2085                  drk.stop_rpc_client().await
2086              }
2087  
2088              ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
2089                  let drk = new_wallet(
2090                      blockchain_config.cache_path,
2091                      blockchain_config.wallet_path,
2092                      blockchain_config.wallet_pass,
2093                      None,
2094                      &ex,
2095                      args.fun,
2096                  )
2097                  .await;
2098  
2099                  if let Some(c) = tx_hash {
2100                      let (tx_hash, status, block_height, tx) = drk.get_tx_history_record(&c).await?;
2101  
2102                      if encode {
2103                          println!("{}", base64::encode(&serialize_async(&tx).await));
2104                          exit(1)
2105                      }
2106  
2107                      println!("Transaction ID: {tx_hash}");
2108                      println!("Status: {status}");
2109                      match block_height {
2110                          Some(block_height) => println!("Block height: {block_height}"),
2111                          None => println!("Block height: -"),
2112                      }
2113                      println!("{tx:?}");
2114  
2115                      return Ok(())
2116                  }
2117  
2118                  let map = match drk.get_txs_history() {
2119                      Ok(m) => m,
2120                      Err(e) => {
2121                          eprintln!("Failed to retrieve transactions history records: {e}");
2122                          exit(2);
2123                      }
2124                  };
2125  
2126                  // Create a prettytable with the new data:
2127                  let mut table = Table::new();
2128                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2129                  table.set_titles(row!["Transaction Hash", "Status", "Block Height"]);
2130                  for (txs_hash, status, block_height) in map.iter() {
2131                      let block_height = match block_height {
2132                          Some(block_height) => block_height.to_string(),
2133                          None => String::from("-"),
2134                      };
2135                      table.add_row(row![txs_hash, status, block_height]);
2136                  }
2137  
2138                  if table.is_empty() {
2139                      println!("No transactions found");
2140                  } else {
2141                      println!("{table}");
2142                  }
2143  
2144                  Ok(())
2145              }
2146  
2147              ExplorerSubcmd::ClearReverted => {
2148                  let drk = new_wallet(
2149                      blockchain_config.cache_path,
2150                      blockchain_config.wallet_path,
2151                      blockchain_config.wallet_pass,
2152                      None,
2153                      &ex,
2154                      args.fun,
2155                  )
2156                  .await;
2157  
2158                  let mut output = vec![];
2159                  if let Err(e) = drk.remove_reverted_txs(&mut output) {
2160                      print_output(&output);
2161                      eprintln!("Failed to remove reverted transactions: {e}");
2162                      exit(2);
2163                  };
2164                  print_output(&output);
2165  
2166                  Ok(())
2167              }
2168  
2169              ExplorerSubcmd::ScannedBlocks { height } => {
2170                  let drk = new_wallet(
2171                      blockchain_config.cache_path,
2172                      blockchain_config.wallet_path,
2173                      blockchain_config.wallet_pass,
2174                      None,
2175                      &ex,
2176                      args.fun,
2177                  )
2178                  .await;
2179  
2180                  if let Some(height) = height {
2181                      let hash = match drk.get_scanned_block_hash(&height) {
2182                          Ok(h) => h,
2183                          Err(e) => {
2184                              eprintln!("Failed to retrieve scanned block record: {e}");
2185                              exit(2);
2186                          }
2187                      };
2188  
2189                      println!("Height: {height}");
2190                      println!("Hash: {hash}");
2191  
2192                      return Ok(())
2193                  }
2194  
2195                  let map = match drk.get_scanned_block_records() {
2196                      Ok(m) => m,
2197                      Err(e) => {
2198                          eprintln!("Failed to retrieve scanned blocks records: {e}");
2199                          exit(2);
2200                      }
2201                  };
2202  
2203                  // Create a prettytable with the new data:
2204                  let mut table = Table::new();
2205                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2206                  table.set_titles(row!["Height", "Hash"]);
2207                  for (height, hash) in map.iter() {
2208                      table.add_row(row![height, hash]);
2209                  }
2210  
2211                  if table.is_empty() {
2212                      println!("No scanned blocks records found");
2213                  } else {
2214                      println!("{table}");
2215                  }
2216  
2217                  Ok(())
2218              }
2219          },
2220  
2221          Subcmd::Alias { command } => match command {
2222              AliasSubcmd::Add { alias, token } => {
2223                  if alias.chars().count() > 5 {
2224                      eprintln!("Error: Alias exceeds 5 characters");
2225                      exit(2);
2226                  }
2227  
2228                  let token_id = match TokenId::from_str(token.as_str()) {
2229                      Ok(t) => t,
2230                      Err(e) => {
2231                          eprintln!("Invalid Token ID: {e}");
2232                          exit(2);
2233                      }
2234                  };
2235  
2236                  let drk = new_wallet(
2237                      blockchain_config.cache_path,
2238                      blockchain_config.wallet_path,
2239                      blockchain_config.wallet_pass,
2240                      None,
2241                      &ex,
2242                      args.fun,
2243                  )
2244                  .await;
2245                  let mut output = vec![];
2246                  if let Err(e) = drk.add_alias(alias, token_id, &mut output).await {
2247                      print_output(&output);
2248                      eprintln!("Failed to add alias: {e}");
2249                      exit(2);
2250                  }
2251                  print_output(&output);
2252  
2253                  Ok(())
2254              }
2255  
2256              AliasSubcmd::Show { alias, token } => {
2257                  let token_id = match token {
2258                      Some(t) => match TokenId::from_str(t.as_str()) {
2259                          Ok(t) => Some(t),
2260                          Err(e) => {
2261                              eprintln!("Invalid Token ID: {e}");
2262                              exit(2);
2263                          }
2264                      },
2265                      None => None,
2266                  };
2267  
2268                  let drk = new_wallet(
2269                      blockchain_config.cache_path,
2270                      blockchain_config.wallet_path,
2271                      blockchain_config.wallet_pass,
2272                      None,
2273                      &ex,
2274                      args.fun,
2275                  )
2276                  .await;
2277                  let map = drk.get_aliases(alias, token_id).await?;
2278  
2279                  // Create a prettytable with the new data:
2280                  let mut table = Table::new();
2281                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2282                  table.set_titles(row!["Alias", "Token ID"]);
2283                  for (alias, token_id) in map.iter() {
2284                      table.add_row(row![alias, token_id]);
2285                  }
2286  
2287                  if table.is_empty() {
2288                      println!("No aliases found");
2289                  } else {
2290                      println!("{table}");
2291                  }
2292  
2293                  Ok(())
2294              }
2295  
2296              AliasSubcmd::Remove { alias } => {
2297                  let drk = new_wallet(
2298                      blockchain_config.cache_path,
2299                      blockchain_config.wallet_path,
2300                      blockchain_config.wallet_pass,
2301                      None,
2302                      &ex,
2303                      args.fun,
2304                  )
2305                  .await;
2306                  let mut output = vec![];
2307                  if let Err(e) = drk.remove_alias(alias, &mut output).await {
2308                      print_output(&output);
2309                      eprintln!("Failed to remove alias: {e}");
2310                      exit(2);
2311                  }
2312                  print_output(&output);
2313  
2314                  Ok(())
2315              }
2316          },
2317  
2318          Subcmd::Token { command } => match command {
2319              TokenSubcmd::Import { secret_key, token_blind } => {
2320                  let mint_authority = match SecretKey::from_str(&secret_key) {
2321                      Ok(ma) => ma,
2322                      Err(e) => {
2323                          eprintln!("Invalid mint authority: {e}");
2324                          exit(2);
2325                      }
2326                  };
2327  
2328                  let token_blind = match BaseBlind::from_str(&token_blind) {
2329                      Ok(tb) => tb,
2330                      Err(e) => {
2331                          eprintln!("Invalid token blind: {e}");
2332                          exit(2);
2333                      }
2334                  };
2335  
2336                  let drk = new_wallet(
2337                      blockchain_config.cache_path,
2338                      blockchain_config.wallet_path,
2339                      blockchain_config.wallet_pass,
2340                      None,
2341                      &ex,
2342                      args.fun,
2343                  )
2344                  .await;
2345                  let token_id = drk.import_mint_authority(mint_authority, token_blind).await?;
2346                  println!("Successfully imported mint authority for token ID: {token_id}");
2347  
2348                  Ok(())
2349              }
2350  
2351              TokenSubcmd::GenerateMint => {
2352                  let drk = new_wallet(
2353                      blockchain_config.cache_path,
2354                      blockchain_config.wallet_path,
2355                      blockchain_config.wallet_pass,
2356                      None,
2357                      &ex,
2358                      args.fun,
2359                  )
2360                  .await;
2361                  let mint_authority = SecretKey::random(&mut OsRng);
2362                  let token_blind = BaseBlind::random(&mut OsRng);
2363                  let token_id = drk.import_mint_authority(mint_authority, token_blind).await?;
2364                  println!("Successfully imported mint authority for token ID: {token_id}");
2365  
2366                  Ok(())
2367              }
2368  
2369              TokenSubcmd::List => {
2370                  let drk = new_wallet(
2371                      blockchain_config.cache_path,
2372                      blockchain_config.wallet_path,
2373                      blockchain_config.wallet_pass,
2374                      None,
2375                      &ex,
2376                      args.fun,
2377                  )
2378                  .await;
2379                  let tokens = drk.get_mint_authorities().await?;
2380                  let aliases_map = match drk.get_aliases_mapped_by_token().await {
2381                      Ok(map) => map,
2382                      Err(e) => {
2383                          eprintln!("Failed to fetch wallet aliases: {e}");
2384                          exit(2);
2385                      }
2386                  };
2387  
2388                  let mut table = Table::new();
2389                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2390                  table.set_titles(row![
2391                      "Token ID",
2392                      "Aliases",
2393                      "Mint Authority",
2394                      "Token Blind",
2395                      "Frozen",
2396                      "Freeze Height"
2397                  ]);
2398  
2399                  for (token_id, authority, blind, frozen, freeze_height) in tokens {
2400                      let aliases = match aliases_map.get(&token_id.to_string()) {
2401                          Some(a) => a,
2402                          None => "-",
2403                      };
2404  
2405                      let freeze_height = match freeze_height {
2406                          Some(freeze_height) => freeze_height.to_string(),
2407                          None => String::from("-"),
2408                      };
2409  
2410                      table.add_row(row![token_id, aliases, authority, blind, frozen, freeze_height]);
2411                  }
2412  
2413                  if table.is_empty() {
2414                      println!("No tokens found");
2415                  } else {
2416                      println!("{table}");
2417                  }
2418  
2419                  Ok(())
2420              }
2421  
2422              TokenSubcmd::Mint { token, amount, recipient, spend_hook, user_data } => {
2423                  let drk = new_wallet(
2424                      blockchain_config.cache_path,
2425                      blockchain_config.wallet_path,
2426                      blockchain_config.wallet_pass,
2427                      Some(blockchain_config.endpoint),
2428                      &ex,
2429                      args.fun,
2430                  )
2431                  .await;
2432  
2433                  if let Err(e) = f64::from_str(&amount) {
2434                      eprintln!("Invalid amount: {e}");
2435                      exit(2);
2436                  }
2437  
2438                  let rcpt = match PublicKey::from_str(&recipient) {
2439                      Ok(r) => r,
2440                      Err(e) => {
2441                          eprintln!("Invalid recipient: {e}");
2442                          exit(2);
2443                      }
2444                  };
2445  
2446                  let token_id = match drk.get_token(token).await {
2447                      Ok(t) => t,
2448                      Err(e) => {
2449                          eprintln!("Invalid Token ID: {e}");
2450                          exit(2);
2451                      }
2452                  };
2453  
2454                  let spend_hook = match spend_hook {
2455                      Some(s) => match FuncId::from_str(&s) {
2456                          Ok(s) => Some(s),
2457                          Err(e) => {
2458                              eprintln!("Invalid spend hook: {e}");
2459                              exit(2);
2460                          }
2461                      },
2462                      None => None,
2463                  };
2464  
2465                  let user_data = match user_data {
2466                      Some(u) => {
2467                          let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
2468                              Ok(b) => b,
2469                              Err(e) => {
2470                                  eprintln!("Invalid user data: {e:?}");
2471                                  exit(2);
2472                              }
2473                          };
2474  
2475                          match pallas::Base::from_repr(bytes).into() {
2476                              Some(v) => Some(v),
2477                              None => {
2478                                  eprintln!("Invalid user data");
2479                                  exit(2);
2480                              }
2481                          }
2482                      }
2483                      None => None,
2484                  };
2485  
2486                  let tx = match drk.mint_token(&amount, rcpt, token_id, spend_hook, user_data).await
2487                  {
2488                      Ok(tx) => tx,
2489                      Err(e) => {
2490                          eprintln!("Failed to create token mint transaction: {e}");
2491                          exit(2);
2492                      }
2493                  };
2494  
2495                  println!("{}", base64::encode(&serialize_async(&tx).await));
2496  
2497                  drk.stop_rpc_client().await
2498              }
2499  
2500              TokenSubcmd::Freeze { token } => {
2501                  let drk = new_wallet(
2502                      blockchain_config.cache_path,
2503                      blockchain_config.wallet_path,
2504                      blockchain_config.wallet_pass,
2505                      Some(blockchain_config.endpoint),
2506                      &ex,
2507                      args.fun,
2508                  )
2509                  .await;
2510                  let token_id = match drk.get_token(token).await {
2511                      Ok(t) => t,
2512                      Err(e) => {
2513                          eprintln!("Invalid Token ID: {e}");
2514                          exit(2);
2515                      }
2516                  };
2517  
2518                  let tx = match drk.freeze_token(token_id).await {
2519                      Ok(tx) => tx,
2520                      Err(e) => {
2521                          eprintln!("Failed to create token freeze transaction: {e}");
2522                          exit(2);
2523                      }
2524                  };
2525  
2526                  println!("{}", base64::encode(&serialize_async(&tx).await));
2527  
2528                  drk.stop_rpc_client().await
2529              }
2530          },
2531  
2532          Subcmd::Contract { command } => match command {
2533              ContractSubcmd::GenerateDeploy => {
2534                  let drk = new_wallet(
2535                      blockchain_config.cache_path,
2536                      blockchain_config.wallet_path,
2537                      blockchain_config.wallet_pass,
2538                      None,
2539                      &ex,
2540                      args.fun,
2541                  )
2542                  .await;
2543  
2544                  let mut output = vec![];
2545                  if let Err(e) = drk.deploy_auth_keygen(&mut output).await {
2546                      print_output(&output);
2547                      eprintln!("Error creating deploy auth keypair: {e}");
2548                      exit(2);
2549                  }
2550                  print_output(&output);
2551  
2552                  Ok(())
2553              }
2554  
2555              ContractSubcmd::List { contract_id } => {
2556                  let drk = new_wallet(
2557                      blockchain_config.cache_path,
2558                      blockchain_config.wallet_path,
2559                      blockchain_config.wallet_pass,
2560                      None,
2561                      &ex,
2562                      args.fun,
2563                  )
2564                  .await;
2565  
2566                  if let Some(contract_id) = contract_id {
2567                      let contract_id = match ContractId::from_str(&contract_id) {
2568                          Ok(d) => d,
2569                          Err(e) => {
2570                              eprintln!("Invalid contract id: {e}");
2571                              exit(2);
2572                          }
2573                      };
2574  
2575                      let history = drk.get_deploy_auth_history(&contract_id).await?;
2576  
2577                      let mut table = Table::new();
2578                      table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2579                      table.set_titles(row!["Transaction Hash", "Type", "Block Height"]);
2580  
2581                      for (tx_hash, tx_type, block_height) in history {
2582                          table.add_row(row![tx_hash, tx_type, block_height]);
2583                      }
2584  
2585                      if table.is_empty() {
2586                          println!("No history records found");
2587                      } else {
2588                          println!("{table}");
2589                      }
2590  
2591                      return Ok(())
2592                  }
2593  
2594                  let auths = drk.list_deploy_auth().await?;
2595  
2596                  let mut table = Table::new();
2597                  table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2598                  table.set_titles(row!["Contract ID", "Secret Key", "Locked", "Lock Height"]);
2599  
2600                  for (contract_id, secret_key, is_locked, lock_height) in auths {
2601                      let lock_height = match lock_height {
2602                          Some(lock_height) => lock_height.to_string(),
2603                          None => String::from("-"),
2604                      };
2605                      table.add_row(row![contract_id, secret_key, is_locked, lock_height]);
2606                  }
2607  
2608                  if table.is_empty() {
2609                      println!("No deploy authorities found");
2610                  } else {
2611                      println!("{table}");
2612                  }
2613  
2614                  Ok(())
2615              }
2616  
2617              ContractSubcmd::ExportData { tx_hash } => {
2618                  let drk = new_wallet(
2619                      blockchain_config.cache_path,
2620                      blockchain_config.wallet_path,
2621                      blockchain_config.wallet_pass,
2622                      None,
2623                      &ex,
2624                      args.fun,
2625                  )
2626                  .await;
2627  
2628                  let pair = drk.get_deploy_history_record_data(&tx_hash).await?;
2629  
2630                  println!("{}", base64::encode(&serialize_async(&pair).await));
2631  
2632                  Ok(())
2633              }
2634  
2635              ContractSubcmd::Deploy { deploy_auth, wasm_path, deploy_ix } => {
2636                  // Parse the deployment authority contract id
2637                  let deploy_auth = match ContractId::from_str(&deploy_auth) {
2638                      Ok(d) => d,
2639                      Err(e) => {
2640                          eprintln!("Invalid deploy authority: {e}");
2641                          exit(2);
2642                      }
2643                  };
2644  
2645                  // Read the wasm bincode and deploy instruction
2646                  let wasm_bin = smol::fs::read(expand_path(&wasm_path)?).await?;
2647                  let deploy_ix = match deploy_ix {
2648                      Some(p) => smol::fs::read(expand_path(&p)?).await?,
2649                      None => vec![],
2650                  };
2651  
2652                  let drk = new_wallet(
2653                      blockchain_config.cache_path,
2654                      blockchain_config.wallet_path,
2655                      blockchain_config.wallet_pass,
2656                      Some(blockchain_config.endpoint),
2657                      &ex,
2658                      args.fun,
2659                  )
2660                  .await;
2661  
2662                  let tx = match drk.deploy_contract(&deploy_auth, wasm_bin, deploy_ix).await {
2663                      Ok(tx) => tx,
2664                      Err(e) => {
2665                          eprintln!("Error creating contract deployment tx: {e}");
2666                          exit(2);
2667                      }
2668                  };
2669  
2670                  println!("{}", base64::encode(&serialize_async(&tx).await));
2671  
2672                  drk.stop_rpc_client().await
2673              }
2674  
2675              ContractSubcmd::Lock { deploy_auth } => {
2676                  // Parse the deployment authority contract id
2677                  let deploy_auth = match ContractId::from_str(&deploy_auth) {
2678                      Ok(d) => d,
2679                      Err(e) => {
2680                          eprintln!("Invalid deploy authority: {e}");
2681                          exit(2);
2682                      }
2683                  };
2684  
2685                  let drk = new_wallet(
2686                      blockchain_config.cache_path,
2687                      blockchain_config.wallet_path,
2688                      blockchain_config.wallet_pass,
2689                      Some(blockchain_config.endpoint),
2690                      &ex,
2691                      args.fun,
2692                  )
2693                  .await;
2694  
2695                  let tx = match drk.lock_contract(&deploy_auth).await {
2696                      Ok(tx) => tx,
2697                      Err(e) => {
2698                          eprintln!("Error creating contract lock tx: {e}");
2699                          exit(2);
2700                      }
2701                  };
2702  
2703                  println!("{}", base64::encode(&serialize_async(&tx).await));
2704  
2705                  drk.stop_rpc_client().await
2706              }
2707          },
2708      }
2709  }