text: // Copyright (c) 2019-2021 Fango Developers
  // Copyright (c) 2018-2021 Fandom Gold Society
  // Copyright (c) 2018-2019 Conceal Network & Conceal Devs
  // Copyright (c) 2016-2019 The Karbowanec developers
  // Copyright (c) 2012-2018 The CryptoNote developers
  //
  // This file is part of Fango.
  //
  // Fango is free software distributed in the hope that it
  // will be useful, but WITHOUT ANY WARRANTY; without even the
  // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  // PURPOSE. You can redistribute it and/or modify it under the terms
  // of the GNU General Public License v3 or later versions as published
  // by the Free Software Foundation. Fango includes elements written 
  // by third parties. See file labeled LICENSE for more details.
  // You should have received a copy of the GNU General Public License
  // along with Fango. If not, see <https://www.gnu.org/licenses/>.
  
  #include "SimpleWallet.h"
  
  #include <ctime>
  #include <fstream>
  #include <future>
  #include <iomanip>
  #include <thread>
  #include <set>
  #include <sstream>
  #include <regex>
  
  #include <boost/format.hpp>
  #include <boost/bind.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/program_options.hpp>
  #include <boost/algorithm/string.hpp>
  #include <boost/filesystem.hpp>
  
  #include "Common/Base58.h"
  #include "Common/CommandLine.h"
  #include "Common/SignalHandler.h"
  #include "Common/StringTools.h"
  #include "Common/PathTools.h"
  #include "Common/Util.h"
  #include "Common/DnsTools.h"
  #include "CryptoNoteCore/CryptoNoteFormatUtils.h"
  #include "CryptoNoteProtocol/CryptoNoteProtocolHandler.h"
  #include "NodeRpcProxy/NodeRpcProxy.h"
  #include "Rpc/CoreRpcServerCommandsDefinitions.h"
  #include "Rpc/HttpClient.h"
  #include "CryptoNoteCore/CryptoNoteTools.h"
  
  #include "Wallet/WalletRpcServer.h"
  #include "WalletLegacy/WalletLegacy.h"
  #include "Wallet/LegacyKeysImporter.h"
  #include "WalletLegacy/WalletHelper.h"
  #include "Mnemonics/electrum-words.cpp"
  
  #include "version.h"
  
  #include <Logging/LoggerManager.h>
  
  #if defined(WIN32)
  #include <crtdbg.h>
  #endif
  
  using namespace CryptoNote;
  using namespace Logging;
  using Common::JsonValue;
  
  namespace po = boost::program_options;
  
  #define EXTENDED_LOGS_FILE "wallet_details.log"
  #undef ERROR
  
  namespace {
  
  const command_line::arg_descriptor<std::string> arg_wallet_file = { "wallet-file", "Use wallet <arg>", "" };
  const command_line::arg_descriptor<std::string> arg_generate_new_wallet = { "generate-new-wallet", "Generate new wallet and save it to <arg>", "" };
  const command_line::arg_descriptor<std::string> arg_daemon_address = { "daemon-address", "Use daemon instance at <host>:<port>", "" };
  const command_line::arg_descriptor<std::string> arg_daemon_host = { "daemon-host", "Use daemon instance at host <arg> instead of localhost", "" };
  const command_line::arg_descriptor<std::string> arg_password = { "password", "Wallet password", "", true };
  const command_line::arg_descriptor<uint16_t>    arg_daemon_port = { "daemon-port", "Use daemon instance at port <arg> instead of default", 0 };
  const command_line::arg_descriptor<uint32_t>    arg_log_level = { "set_log", "", INFO, true };
  const command_line::arg_descriptor<bool>        arg_testnet = { "testnet", "Used to deploy test nets. The daemon must be launched with --testnet flag", false };
  const command_line::arg_descriptor< std::vector<std::string> > arg_command = { "command", "" };
  
  bool parseUrlAddress(const std::string& url, std::string& address, uint16_t& port) {
    auto pos = url.find("://");
    size_t addrStart = 0;
  
    if (pos != std::string::npos) {
      addrStart = pos + 3;
    }
  
    auto addrEnd = url.find(':', addrStart);
  
    if (addrEnd != std::string::npos) {
      auto portEnd = url.find('/', addrEnd);
      port = Common::fromString<uint16_t>(url.substr(
        addrEnd + 1, portEnd == std::string::npos ? std::string::npos : portEnd - addrEnd - 1));
    } else {
      addrEnd = url.find('/');
      port = 80;
    }
  
    address = url.substr(addrStart, addrEnd - addrStart);
    return true;
  }
  
  
  inline std::string interpret_rpc_response(bool ok, const std::string& status) {
    std::string err;
    if (ok) {
      if (status == CORE_RPC_STATUS_BUSY) {
        err = "daemon is busy. Please try later";
      } else if (status != CORE_RPC_STATUS_OK) {
        err = status;
      }
    } else {
      err = "possible lost connection to daemon";
    }
    return err;
  }
  
  template <typename IterT, typename ValueT = typename IterT::value_type>
  class ArgumentReader {
  public:
  
    ArgumentReader(IterT begin, IterT end) :
      m_begin(begin), m_end(end), m_cur(begin) {
    }
  
    bool eof() const {
      return m_cur == m_end;
    }
  
    ValueT next() {
      if (eof()) {
        throw std::runtime_error("unexpected end of arguments");
      }
  
      return *m_cur++;
    }
  
  private:
  
    IterT m_cur;
    IterT m_begin;
    IterT m_end;
  };
  
  struct TransferCommand {
    const CryptoNote::Currency& m_currency;
    size_t fake_outs_count;
    std::vector<CryptoNote::WalletLegacyTransfer> dsts;
    std::vector<uint8_t> extra;
    uint64_t fee;
    std::map<std::string, std::vector<WalletLegacyTransfer>> aliases;
    std::vector<std::string> messages;
    uint64_t ttl;
  
    TransferCommand(const CryptoNote::Currency& currency) :
      m_currency(currency), fake_outs_count(0), fee(currency.minimumFee()), ttl(0) {
    }
  
  /* This parses arguments from the transfer command */
    bool parseArguments(LoggerRef& logger, const std::vector<std::string> &args) {
      ArgumentReader<std::vector<std::string>::const_iterator> ar(args.begin(), args.end());
  
      try 
      {
        /* Parse the remaining arguments */
        while (!ar.eof()) 
        {
          auto arg = ar.next();
  
          if (arg.size() && arg[0] == '-') 
          {          
            const auto& value = ar.next();
            if (arg == "-p") {
              if (!createTxExtraWithPaymentId(value, extra)) {
                logger(ERROR, BRIGHT_RED) << "payment ID has invalid format: \"" << value << "\", expected 64-character string";
                return false;
              }
            } else if (arg == "-m") {
              messages.emplace_back(value);
            } else if (arg == "-ttl") {
              fee = 0;
              if (!Common::fromString(value, ttl) || ttl < 1 || ttl * 60 > m_currency.mempoolTxLiveTime()) {
                logger(ERROR, BRIGHT_RED) << "TTL has invalid format: \"" << value << "\", " <<
                  "enter time from 1 to " << (m_currency.mempoolTxLiveTime() / 60) << " minutes";
                return false;
              }
            }
          } else {
  
            /* Integrated address check */
            if (arg.length() == 186) {
              std::string paymentID;
              std::string spendPublicKey;
              std::string viewPublicKey;
              const uint64_t paymentIDLen = 64;
  
              /* Extract the payment id */
              std::string decoded;
              uint64_t prefix;
              if (Tools::Base58::decode_addr(arg, prefix, decoded)) {
                paymentID = decoded.substr(0, paymentIDLen);
              }
  
              /* Validate and add the payment ID to extra */
              if (!createTxExtraWithPaymentId(paymentID, extra)) {
                logger(ERROR, BRIGHT_RED) << "Integrated payment ID has invalid format: \"" << paymentID << "\", expected 64-character string";
                return false;
              }
  
              /* create the address from the public keys */
              std::string keys = decoded.substr(paymentIDLen, std::string::npos);
              CryptoNote::AccountPublicAddress addr;
              CryptoNote::BinaryArray ba = Common::asBinaryArray(keys);
  
              if (!CryptoNote::fromBinaryArray(addr, ba)) {
                return true;
              }
  
              std::string address = CryptoNote::getAccountAddressAsStr(CryptoNote::parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, 
                                                                      addr);   
              arg = address;
            }
  
            WalletLegacyTransfer destination;
            WalletLegacyTransfer feeDestination;          
            CryptoNote::TransactionDestinationEntry de;
            std::string aliasUrl;
  
            if (!m_currency.parseAccountAddressString(arg, de.addr)) {
              aliasUrl = arg;
            }
  
            auto value = ar.next();
            bool ok = m_currency.parseAmount(value, de.amount);
  
            if (!ok || 0 == de.amount) {
              logger(ERROR, BRIGHT_RED) << "amount is wrong: " << arg << ' ' << value <<
                ", expected number from 0 to " << m_currency.formatAmount(std::numeric_limits<uint64_t>::max());
              return false;
            }
  
            if (aliasUrl.empty()) {
              destination.address = arg;
              destination.amount = de.amount;
              dsts.push_back(destination);
            } else {
              aliases[aliasUrl].emplace_back(WalletLegacyTransfer{"", static_cast<int64_t>(de.amount)});
            }
  
            /* Remote node transactions fees are 10000 X */
            if (!remote_fee_address.empty()) {
              destination.address = remote_fee_address;                     
              int64_t remote_node_fee = static_cast<int64_t>(de.amount * 0.0025);
              if (remote_node_fee > 100000000)
                  remote_node_fee = 100000000;
              destination.amount = remote_node_fee;
              dsts.push_back(destination);
            }
  
          }
        }
  
        if (dsts.empty() && aliases.empty()) {
          logger(ERROR, BRIGHT_RED) << "At least one destination address is required";
          return false;
        }
      } catch (const std::exception& e) {
        logger(ERROR, BRIGHT_RED) << e.what();
        return false;
      }
  
      return true;
    }
  };
  
  JsonValue buildLoggerConfiguration(Level level, const std::string& logfile) {
    JsonValue loggerConfiguration(JsonValue::OBJECT);
    loggerConfiguration.insert("globalLevel", static_cast<int64_t>(level));
  
    JsonValue& cfgLoggers = loggerConfiguration.insert("loggers", JsonValue::ARRAY);
  
    JsonValue& consoleLogger = cfgLoggers.pushBack(JsonValue::OBJECT);
    consoleLogger.insert("type", "console");
    consoleLogger.insert("level", static_cast<int64_t>(TRACE));
    consoleLogger.insert("pattern", "");
  
    JsonValue& fileLogger = cfgLoggers.pushBack(JsonValue::OBJECT);
    fileLogger.insert("type", "file");
    fileLogger.insert("filename", logfile);
    fileLogger.insert("level", static_cast<int64_t>(TRACE));
  
    return loggerConfiguration;
  }
  
  std::error_code initAndLoadWallet(IWalletLegacy& wallet, std::istream& walletFile, const std::string& password) {
    WalletHelper::InitWalletResultObserver initObserver;
    std::future<std::error_code> f_initError = initObserver.initResult.get_future();
  
    WalletHelper::IWalletRemoveObserverGuard removeGuard(wallet, initObserver);
    wallet.initAndLoad(walletFile, password);
    auto initError = f_initError.get();
  
    return initError;
  }
  
  std::string tryToOpenWalletOrLoadKeysOrThrow(LoggerRef& logger, std::unique_ptr<IWalletLegacy>& wallet, const std::string& walletFile, const std::string& password) {
    std::string keys_file, walletFileName;
    WalletHelper::prepareFileNames(walletFile, keys_file, walletFileName);
  
    boost::system::error_code ignore;
    bool keysExists = boost::filesystem::exists(keys_file, ignore);
    bool walletExists = boost::filesystem::exists(walletFileName, ignore);
    if (!walletExists && !keysExists && boost::filesystem::exists(walletFile, ignore)) {
      boost::system::error_code renameEc;
      boost::filesystem::rename(walletFile, walletFileName, renameEc);
      if (renameEc) {
        throw std::runtime_error("failed to rename file '" + walletFile + "' to '" + walletFileName + "': " + renameEc.message());
      }
  
      walletExists = true;
    }
  
    if (walletExists) {
      logger(INFO) << "Loading wallet...";
      std::ifstream walletFile;
      walletFile.open(walletFileName, std::ios_base::binary | std::ios_base::in);
      if (walletFile.fail()) {
        throw std::runtime_error("error opening wallet file '" + walletFileName + "'");
      }
  
      auto initError = initAndLoadWallet(*wallet, walletFile, password);
  
      walletFile.close();
      if (initError) { //bad password, or legacy format
        if (keysExists) {
          std::stringstream ss;
          CryptoNote::importLegacyKeys(keys_file, password, ss);
          boost::filesystem::rename(keys_file, keys_file + ".back");
          boost::filesystem::rename(walletFileName, walletFileName + ".back");
  
          initError = initAndLoadWallet(*wallet, ss, password);
          if (initError) {
            throw std::runtime_error("failed to load wallet: " + initError.message());
          }
  
          logger(INFO) << "Storing wallet...";
  
          try {
            CryptoNote::WalletHelper::storeWallet(*wallet, walletFileName);
          } catch (std::exception& e) {
            logger(ERROR, BRIGHT_RED) << "Failed to store wallet: " << e.what();
            throw std::runtime_error("error saving wallet file '" + walletFileName + "'");
          }
  
          logger(INFO, BRIGHT_GREEN) << "Stored ok";
          return walletFileName;
        } else { // no keys, wallet error loading
          throw std::runtime_error("can't load wallet file '" + walletFileName + "', check password");
        }
      } else { //new wallet ok
        return walletFileName;
      }
    } else if (keysExists) { //wallet not exists but keys presented
      std::stringstream ss;
      CryptoNote::importLegacyKeys(keys_file, password, ss);
      boost::filesystem::rename(keys_file, keys_file + ".back");
  
      WalletHelper::InitWalletResultObserver initObserver;
      std::future<std::error_code> f_initError = initObserver.initResult.get_future();
  
      WalletHelper::IWalletRemoveObserverGuard removeGuard(*wallet, initObserver);
      wallet->initAndLoad(ss, password);
      auto initError = f_initError.get();
  
      removeGuard.removeObserver();
      if (initError) {
        throw std::runtime_error("failed to load wallet: " + initError.message());
      }
  
      logger(INFO) << "Storing wallet...";
  
      try {
        CryptoNote::WalletHelper::storeWallet(*wallet, walletFileName);
      } catch(std::exception& e) {
        logger(ERROR, BRIGHT_RED) << "Failed to store wallet: " << e.what();
        throw std::runtime_error("error saving wallet file '" + walletFileName + "'");
      }
  
      logger(INFO, BRIGHT_GREEN) << "Stored ok";
      return walletFileName;
    } else { //no wallet no keys
      throw std::runtime_error("wallet file '" + walletFileName + "' is not found");
    }
  }
  
  std::string makeCenteredString(size_t width, const std::string& text) {
    if (text.size() >= width) {
      return text;
    }
  
    size_t offset = (width - text.size() + 1) / 2;
    return std::string(offset, ' ') + text + std::string(width - text.size() - offset, ' ');
  }
  
  const size_t TIMESTAMP_MAX_WIDTH = 19;
  const size_t HASH_MAX_WIDTH = 64;
  const size_t TOTAL_AMOUNT_MAX_WIDTH = 20;
  const size_t FEE_MAX_WIDTH = 14;
  const size_t BLOCK_MAX_WIDTH = 7;
  const size_t UNLOCK_TIME_MAX_WIDTH = 11;
  
  void printListTransfersHeader(LoggerRef& logger) {
    std::string header = makeCenteredString(TIMESTAMP_MAX_WIDTH, "timestamp (UTC)") + "  ";
    header += makeCenteredString(HASH_MAX_WIDTH, "hash") + "  ";
    header += makeCenteredString(TOTAL_AMOUNT_MAX_WIDTH, "total amount") + "  ";
    header += makeCenteredString(FEE_MAX_WIDTH, "fee") + "  ";
    header += makeCenteredString(BLOCK_MAX_WIDTH, "block") + "  ";
    header += makeCenteredString(UNLOCK_TIME_MAX_WIDTH, "unlock time");
  
    logger(INFO) << header;
    logger(INFO) << std::string(header.size(), '-');
  }
  
  void printListTransfersItem(LoggerRef& logger, const WalletLegacyTransaction& txInfo, IWalletLegacy& wallet, const Currency& currency) {
    std::vector<uint8_t> extraVec = Common::asBinaryArray(txInfo.extra);
  
    Crypto::Hash paymentId;
    std::string paymentIdStr = (getPaymentIdFromTxExtra(extraVec, paymentId) && paymentId != NULL_HASH ? Common::podToHex(paymentId) : "");
  
    char timeString[TIMESTAMP_MAX_WIDTH + 1];
    time_t timestamp = static_cast<time_t>(txInfo.timestamp);
    if (std::strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", std::gmtime(&timestamp)) == 0) {
      throw std::runtime_error("time buffer is too small");
    }
  
    std::string rowColor = txInfo.totalAmount < 0 ? MAGENTA : GREEN;
    logger(INFO, rowColor)
      << std::setw(TIMESTAMP_MAX_WIDTH) << timeString
      << "  " << std::setw(HASH_MAX_WIDTH) << Common::podToHex(txInfo.hash)
      << "  " << std::setw(TOTAL_AMOUNT_MAX_WIDTH) << currency.formatAmount(txInfo.totalAmount)
      << "  " << std::setw(FEE_MAX_WIDTH) << currency.formatAmount(txInfo.fee)
      << "  " << std::setw(BLOCK_MAX_WIDTH) << txInfo.blockHeight
      << "  " << std::setw(UNLOCK_TIME_MAX_WIDTH) << txInfo.unlockTime;
  
    if (!paymentIdStr.empty()) {
      logger(INFO, rowColor) << "payment ID: " << paymentIdStr;
    }
  
    if (txInfo.totalAmount < 0) {
      if (txInfo.transferCount > 0) {
        logger(INFO, rowColor) << "transfers:";
        for (TransferId id = txInfo.firstTransferId; id < txInfo.firstTransferId + txInfo.transferCount; ++id) {
          WalletLegacyTransfer tr;
          wallet.getTransfer(id, tr);
          logger(INFO, rowColor) << tr.address << "  " << std::setw(TOTAL_AMOUNT_MAX_WIDTH) << currency.formatAmount(tr.amount);
        }
      }
    }
  
    logger(INFO, rowColor) << " "; //just to make logger print one endline
  }
  
  std::string prepareWalletAddressFilename(const std::string& walletBaseName) {
    return walletBaseName + ".address";
  }
  
  bool writeAddressFile(const std::string& addressFilename, const std::string& address) {
    std::ofstream addressFile(addressFilename, std::ios::out | std::ios::trunc | std::ios::binary);
    if (!addressFile.good()) {
      return false;
    }
  
    addressFile << address;
  
    return true;
  }
  
  bool processServerAliasResponse(const std::string& s, std::string& address) {
    try {
    //   
    // Courtesy of Monero Project
  		// make sure the txt record has "oa1:xfg" and find it
  		auto pos = s.find("oa1:xfg");
  		if (pos == std::string::npos)
  			return false;
  		// search from there to find "recipient_address="
  		pos = s.find("recipient_address=", pos);
  		if (pos == std::string::npos)
  			return false;
  		pos += 18; // move past "recipient_address="
  		// find the next semicolon
  		auto pos2 = s.find(";", pos);
  		if (pos2 != std::string::npos)
  		{
  			// length of address == 98 ccx, we can at least validate that much here
  			if (pos2 - pos == 99)
  			{
  				address = s.substr(pos, 99);
  			} else {
  				return false;
  			}
  		}
      }
  	catch (std::exception&) {
  		return false;
  	}
  
  	return true;
  }
  
  
  
  bool splitUrlToHostAndUri(const std::string& aliasUrl, std::string& host, std::string& uri) {
    size_t protoBegin = aliasUrl.find("http://");
    if (protoBegin != 0 && protoBegin != std::string::npos) {
      return false;
    }
  
    size_t hostBegin = protoBegin == std::string::npos ? 0 : 7; //strlen("http://")
    size_t hostEnd = aliasUrl.find('/', hostBegin);
  
    if (hostEnd == std::string::npos) {
      uri = "/";
      host = aliasUrl.substr(hostBegin);
    } else {
      uri = aliasUrl.substr(hostEnd);
      host = aliasUrl.substr(hostBegin, hostEnd - hostBegin);
    }
  
    return true;
  }
  
  bool askAliasesTransfersConfirmation(const std::map<std::string, std::vector<WalletLegacyTransfer>>& aliases, const Currency& currency) {
    std::cout << "Would you like to send money to the following addresses?" << std::endl;
  
    for (const auto& kv: aliases) {
      for (const auto& transfer: kv.second) {
        std::cout << transfer.address << " " << std::setw(21) << currency.formatAmount(transfer.amount) << "  " << kv.first << std::endl;
      }
    }
  
    std::string answer;
    do {
      std::cout << "y/n: ";
      std::getline(std::cin, answer);
    } while (answer != "y" && answer != "Y" && answer != "n" && answer != "N");
  
    return answer == "y" || answer == "Y";
  }
  
  }
  
  bool processServerFeeAddressResponse(const std::string& response, std::string& fee_address) {
      try {
          std::stringstream stream(response);
          JsonValue json;
          stream >> json;
  
          auto rootIt = json.getObject().find("fee_address");
          if (rootIt == json.getObject().end()) {
              return false;
          }
  
          fee_address = rootIt->second.getString();
      }
      catch (std::exception&) {
          return false;
      }
  
      return true;
  }
  
  std::string simple_wallet::get_commands_str() {
    std::stringstream ss;
    ss << "Commands: " << ENDL;
    std::string usage = m_consoleHandler.getUsage();
    boost::replace_all(usage, "\n", "\n  ");
    usage.insert(0, "  ");
    ss << usage << ENDL;
    return ss.str();
  }
  
  bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) {
    success_msg_writer() << get_commands_str();
    return true;
  }
  
  bool simple_wallet::exit(const std::vector<std::string> &args) {
    m_consoleHandler.requestStop();
    return true;
  }
  
  simple_wallet::simple_wallet(System::Dispatcher& dispatcher, const CryptoNote::Currency& currency, Logging::LoggerManager& log) :
    m_dispatcher(dispatcher),
    m_daemon_port(0),
    m_currency(currency),
    logManager(log),
    logger(log, "simplewallet"),
    m_refresh_progress_reporter(*this),
    m_initResultPromise(nullptr),
    m_walletSynchronized(false) {
    m_consoleHandler.setHandler("create_integrated", boost::bind(&simple_wallet::create_integrated, this, boost::arg<1>()), "create_integrated <payment_id> - Create an integrated address with a payment ID");
    m_consoleHandler.setHandler("export_keys", boost::bind(&simple_wallet::export_keys, this, boost::arg<1>()), "Show the secret keys of the current wallet");
    m_consoleHandler.setHandler("balance", boost::bind(&simple_wallet::show_balance, this, boost::arg<1>()), "Show current wallet balance");
    m_consoleHandler.setHandler("sign_message", boost::bind(&simple_wallet::sign_message, this, boost::arg<1>()), "Sign a message with your wallet keys");
    m_consoleHandler.setHandler("verify_signature", boost::bind(&simple_wallet::verify_signature, this, boost::arg<1>()), "Verify a signed message");
    m_consoleHandler.setHandler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, boost::arg<1>()), "Show incoming transfers");
    m_consoleHandler.setHandler("list_transfers", boost::bind(&simple_wallet::listTransfers, this, boost::arg<1>()), "list_transfers <height> - Show all known transfers from a certain (optional) block height");
    m_consoleHandler.setHandler("payments", boost::bind(&simple_wallet::show_payments, this, boost::arg<1>()), "payments <payment_id_1> [<payment_id_2> ... <payment_id_N>] - Show payments <payment_id_1>, ... <payment_id_N>");
    m_consoleHandler.setHandler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, boost::arg<1>()), "Generate a signature to prove payment: <txid> <address> [<txkey>]");
    m_consoleHandler.setHandler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, boost::arg<1>()), "Show blockchain height");
    m_consoleHandler.setHandler("show_dust", boost::bind(&simple_wallet::show_dust, this, boost::arg<1>()), "Show the number of unmixable dust outputs");
    m_consoleHandler.setHandler("outputs", boost::bind(&simple_wallet::show_num_unlocked_outputs, this, boost::arg<1>()), "Show the number of unlocked outputs available for a transaction");
    m_consoleHandler.setHandler("optimize", boost::bind(&simple_wallet::optimize_outputs, this, boost::arg<1>()), "Combine many available outputs into a few by sending a transaction to self");
    m_consoleHandler.setHandler("optimize_all", boost::bind(&simple_wallet::optimize_all_outputs, this, boost::arg<1>()), "Optimize your wallet several times so you can send large transactions");  
    m_consoleHandler.setHandler("transfer", boost::bind(&simple_wallet::transfer, this, boost::arg<1>()),
      "transfer <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [-p payment_id]"
      " - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. ");
    m_consoleHandler.setHandler("set_log", boost::bind(&simple_wallet::set_log, this, boost::arg<1>()), "set_log <level> - Change current log level, <level> is a number 0-4");
    m_consoleHandler.setHandler("address", boost::bind(&simple_wallet::print_address, this, boost::arg<1>()), "Show current wallet public address");
    m_consoleHandler.setHandler("save", boost::bind(&simple_wallet::save, this, boost::arg<1>()), "Save wallet synchronized data");
    m_consoleHandler.setHandler("reset", boost::bind(&simple_wallet::reset, this, boost::arg<1>()), "Discard cache data and start synchronizing from the start");
    m_consoleHandler.setHandler("help", boost::bind(&simple_wallet::help, this, boost::arg<1>()), "Show this help");
    m_consoleHandler.setHandler("exit", boost::bind(&simple_wallet::exit, this, boost::arg<1>()), "Close wallet");  
    m_consoleHandler.setHandler("get_reserve_proof", boost::bind(&simple_wallet::get_reserve_proof, this, boost::arg<1>()), "all|<amount> [<message>] - Generate a signature proving that you own at least <amount>, optionally with a challenge string <message>. ");
    m_consoleHandler.setHandler("payment_id", boost::bind(&simple_wallet::payment_id, this, _1), "Generate random Payment ID");
  }
  
  /* This function shows the number of outputs in the wallet
    that are below the dust threshold */
  bool simple_wallet::show_dust(const std::vector<std::string>& args) {
    logger(INFO, BRIGHT_WHITE) << "Dust outputs: " << m_wallet->dustBalance() << std::endl;
  	return true;
  }
  
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::set_log(const std::vector<std::string> &args) {
    if (args.size() != 1) {
      fail_msg_writer() << "use: set_log <log_level_number_0-4>";
      return true;
    }
  
    uint16_t l = 0;
    if (!Common::fromString(args[0], l)) {
      fail_msg_writer() << "wrong number format, use: set_log <log_level_number_0-4>";
      return true;
    }
  
    if (l > Logging::TRACE) {
      fail_msg_writer() << "wrong number range, use: set_log <log_level_number_0-4>";
      return true;
    }
  
    logManager.setMaxLevel(static_cast<Logging::Level>(l));
    return true;
  }
  
  bool key_import = true;
  bool simple_wallet::payment_id(const std::vector<std::string> &args) {
    success_msg_writer() << "Payment ID: " << Crypto::rand<Crypto::Hash>();
    return true;
  }
  
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::init(const boost::program_options::variables_map& vm) {
    handle_command_line(vm);
  
    if (!m_daemon_address.empty() && (!m_daemon_host.empty() || 0 != m_daemon_port)) {
      fail_msg_writer() << "you can't specify daemon host or port several times";
      return false;
    }
  
    if (m_daemon_host.empty())
      m_daemon_host = "localhost";
    if (!m_daemon_port)
      m_daemon_port = RPC_DEFAULT_PORT;
  
    if (!m_daemon_address.empty()) 
    {
      if (!parseUrlAddress(m_daemon_address, m_daemon_host, m_daemon_port)) 
      {
        fail_msg_writer() << "failed to parse daemon address: " << m_daemon_address;
        return false;
      }
      remote_fee_address = getFeeAddress();
      logger(INFO, BRIGHT_WHITE) << "Connected to remote node: " << m_daemon_host;
      if (!remote_fee_address.empty()) 
      {
        logger(INFO, BRIGHT_WHITE) << "Fee address: " << remote_fee_address;
      }    
    } 
    else 
    {
      if (!m_daemon_host.empty()) 
        remote_fee_address = getFeeAddress();
  		m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port);
      logger(INFO, BRIGHT_WHITE) << "Connected to remote node: " << m_daemon_host;
      if (!remote_fee_address.empty()) 
      {
        logger(INFO, BRIGHT_WHITE) << "Fee address: " << remote_fee_address;
      }   
    }
  
    if (m_generate_new.empty() && m_wallet_file_arg.empty()) {
      std::cout <<"\n";
      std::cout <<"\n";
      std::cout <<"       8F88888888  A8888 N88b    N8N  .d8888b.   .d88888b.        "<< "\n";
      std::cout <<"       8F8        A88888 N888b   N8N d88P  Y88b d08P` `Y08b       "<< "\n";
      std::cout <<"       8F8       A88P888 N8888b  N8N 8G8    8G8 800     008       "<< "\n";
      std::cout <<"       8F88888  A88P 888 N88Y88b N8N 8G8        800     008       "<< "\n";
      std::cout <<"       8F8     A88P  888 N8N Y88b88N 8G8  8888G 800     008       "<< "\n";
      std::cout <<"       8F8    A88P   888 N8N  Y8888N 8G8    8G8 800     008       "<< "\n";
      std::cout <<"       8F8   A8888888888 N8N   Y888N 8G8b  dGG8 Y80b. .d80P       "<< "\n";
      std::cout <<"       8F8  A88P     888 N8N    Y88N  `Y8888P`   `Y80008P'        "<< "\n";
      std::cout <<"\n";
      std::cout <<"\n";
      std::cout <<"\n";
      std::cout <<  "Welcome to the FANGO command-line wallet."<<"\n";
      std::cout << "Please choose from the following options what you would like to do:\n";
      std::cout << "G - Generate new wallet\n";
      std::cout << "O - Open wallet\n";
      std::cout << "R - Restore backup/paperwallet\n";
      std::cout << "I - Import wallet from keys\n";
      std::cout << "M - Mnemonic seed import\n";
      std::cout << "E - Exit\n";
      char c;
      do {
        std::string answer;
        std::getline(std::cin, answer);
        c = answer[0];
        if (!(c == 'O' || c == 'G' || c == 'E' || c == 'I' || c == 'o' || c == 'g' || c == 'e' || c == 'i' || c == 'm' || c == 'M')) {
          std::cout << "Unknown command: " << c <<std::endl;
        } else {
          break;
        }
      } while (true);
  
      if (c == 'E' || c == 'e') {
        return false;
      }
  
      std::cout << "Specify wallet file name (e.g., name.wallet).\n";
      std::string userInput;
      do {
        std::cout << "Wallet file name: ";
        std::getline(std::cin, userInput);
        boost::algorithm::trim(userInput);
      } while (userInput.empty());
      if (c == 'i' || c == 'I'){
        key_import = true;
        m_import_new = userInput;
      } else if (c == 'm' || c == 'M') {
        key_import = false;
        m_import_new = userInput;
      } else if (c == 'g' || c == 'G') {
        m_generate_new = userInput;
      } else {
        m_wallet_file_arg = userInput;
      }
    }
  
  
  
    if (!m_generate_new.empty() && !m_wallet_file_arg.empty() && !m_import_new.empty()) {
      fail_msg_writer() << "you can't specify 'generate-new-wallet' and 'wallet-file' arguments simultaneously";
      return false;
    }
  
    std::string walletFileName;
    if (!m_generate_new.empty() || !m_import_new.empty()) {
      std::string ignoredString;
      if (!m_generate_new.empty()) {
        WalletHelper::prepareFileNames(m_generate_new, ignoredString, walletFileName);
      } else if (!m_import_new.empty()) {
        WalletHelper::prepareFileNames(m_import_new, ignoredString, walletFileName);
      }
      boost::system::error_code ignore;
      if (boost::filesystem::exists(walletFileName, ignore)) {
        fail_msg_writer() << walletFileName << " already exists";
        return false;
      }
    }
  
  
  
    Tools::PasswordContainer pwd_container;
    if (command_line::has_arg(vm, arg_password)) {
      pwd_container.password(command_line::get_arg(vm, arg_password));
    } else if (!pwd_container.read_password()) {
      fail_msg_writer() << "failed to read wallet password";
      return false;
    }
  
    this->m_node.reset(new NodeRpcProxy(m_daemon_host, m_daemon_port));
  
    std::promise<std::error_code> errorPromise;
    std::future<std::error_code> f_error = errorPromise.get_future();
    auto callback = [&errorPromise](std::error_code e) {errorPromise.set_value(e); };
  
    m_node->addObserver(static_cast<INodeRpcProxyObserver*>(this));
    m_node->init(callback);
    auto error = f_error.get();
    if (error) {
      fail_msg_writer() << "failed to init NodeRPCProxy: " << error.message();
      return false;
    }
  
    if (!m_generate_new.empty()) {
      std::string walletAddressFile = prepareWalletAddressFilename(m_generate_new);
      boost::system::error_code ignore;
      if (boost::filesystem::exists(walletAddressFile, ignore)) {
        logger(ERROR, BRIGHT_RED) << "Address file already exists: " + walletAddressFile;
        return false;
      }
  
      if (!new_wallet(walletFileName, pwd_container.password())) {
        logger(ERROR, BRIGHT_RED) << "account creation failed";
        return false;
      }
  
      if (!writeAddressFile(walletAddressFile, m_wallet->getAddress())) {
        logger(WARNING, BRIGHT_RED) << "Couldn't write wallet address file: " + walletAddressFile;
      }
    } else if (!m_import_new.empty()) {
      std::string walletAddressFile = prepareWalletAddressFilename(m_import_new);
      boost::system::error_code ignore;
      if (boost::filesystem::exists(walletAddressFile, ignore)) {
        logger(ERROR, BRIGHT_RED) << "Address file already exists: " + walletAddressFile;
        return false;
      }
  
      std::string private_spend_key_string;
      std::string private_view_key_string;
  
  Crypto::SecretKey private_spend_key;
  Crypto::SecretKey private_view_key;
  
  if (key_import) {
      do {
        std::cout << "Private Spend Key: ";
        std::getline(std::cin, private_spend_key_string);
        boost::algorithm::trim(private_spend_key_string);
      } while (private_spend_key_string.empty());
      do {
        std::cout << "Private View Key: ";
        std::getline(std::cin, private_view_key_string);
        boost::algorithm::trim(private_view_key_string);
      } while (private_view_key_string.empty());
  } else {
    std::string mnemonic_phrase;
  
    do {
      std::cout << "Mnemonics Phrase (25 words): ";
      std::getline(std::cin, mnemonic_phrase);
      boost::algorithm::trim(mnemonic_phrase);
      boost::algorithm::to_lower(mnemonic_phrase);
    } while (!is_valid_mnemonic(mnemonic_phrase, private_spend_key));
  
    /* This is not used, but is needed to be passed to the function, not sure how we can avoid this */
    Crypto::PublicKey unused_dummy_variable;
  
    AccountBase::generateViewFromSpend(private_spend_key, private_view_key, unused_dummy_variable);
  }
  
  /* We already have our keys if we import via mnemonic seed */
  if (key_import) {
      Crypto::Hash private_spend_key_hash;
      Crypto::Hash private_view_key_hash;
      size_t size;
      if (!Common::fromHex(private_spend_key_string, &private_spend_key_hash, sizeof(private_spend_key_hash), size) || size != sizeof(private_spend_key_hash)) {
        return false;
      }
      if (!Common::fromHex(private_view_key_string, &private_view_key_hash, sizeof(private_view_key_hash), size) || size != sizeof(private_spend_key_hash)) {
        return false;
      }
        private_spend_key = *(struct Crypto::SecretKey *) &private_spend_key_hash;
        private_view_key = *(struct Crypto::SecretKey *) &private_view_key_hash;
      }
  
      if (!new_wallet(private_spend_key, private_view_key, walletFileName, pwd_container.password())) {
        logger(ERROR, BRIGHT_RED) << "account creation failed";
        return false;
      }
  
      if (!writeAddressFile(walletAddressFile, m_wallet->getAddress())) {
        logger(WARNING, BRIGHT_RED) << "Couldn't write wallet address file: " + walletAddressFile;
      }
    } else {
      m_wallet.reset(new WalletLegacy(m_currency, *m_node, logManager));
  
      try {
        m_wallet_file = tryToOpenWalletOrLoadKeysOrThrow(logger, m_wallet, m_wallet_file_arg, pwd_container.password());
      } catch (const std::exception& e) {
        fail_msg_writer() << "failed to load wallet: " << e.what();
        return false;
      }
  
      m_wallet->addObserver(this);
      m_node->addObserver(static_cast<INodeObserver*>(this));
  
      logger(INFO, BRIGHT_WHITE) << "Opened wallet: " << m_wallet->getAddress();
  
      success_msg_writer() <<
        "**********************************************************************\n" <<
        "Use \"help\" command to see the list of available commands.\n" <<
        "**********************************************************************";
    }
  
    return true;
  }
  
  
  //----------------------------------------------------------------------------------------------------
  /* adding support for 25 word electrum seeds. however, we have to ensure that all old wallets that are
  not deterministic, dont get a seed to avoid any loss of funds.
  */
  std::string simple_wallet::generate_mnemonic(Crypto::SecretKey &private_spend_key) {
  
    std::string mnemonic_str;
  
    if (!crypto::ElectrumWords::bytes_to_words(private_spend_key, mnemonic_str, "English")) {
        logger(ERROR, BRIGHT_RED) << "Cant create the mnemonic for the private spend key!";
    }
  
    return mnemonic_str;
  }
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::log_incorrect_words(std::vector<std::string> words) {
    Language::Base *language = Language::Singleton<Language::English>::instance();
    const std::vector<std::string> &dictionary = language->get_word_list();
  
    for (auto i : words) {
      if (std::find(dictionary.begin(), dictionary.end(), i) == dictionary.end()) {
        logger(ERROR, BRIGHT_RED) << i << " is not in the english word list!";
      }
    }
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::is_valid_mnemonic(std::string &mnemonic_phrase, Crypto::SecretKey &private_spend_key) {
  
    static std::string languages[] = {"English"};
    static const int num_of_languages = 1;
    static const int mnemonic_phrase_length = 25;
  
    std::vector<std::string> words;
  
    words = boost::split(words, mnemonic_phrase, ::isspace);
  
    if (words.size() != mnemonic_phrase_length) {
      logger(ERROR, BRIGHT_RED) << "Invalid mnemonic phrase!";
      logger(ERROR, BRIGHT_RED) << "Seed phrase is not 25 words! Please try again.";
      log_incorrect_words(words);
      return false;
    }
  
    for (int i = 0; i < num_of_languages; i++) {
      if (crypto::ElectrumWords::words_to_bytes(mnemonic_phrase, private_spend_key, languages[i])) {
        return true;
      }
    }
  
    logger(ERROR, BRIGHT_RED) << "Invalid mnemonic phrase!";
    log_incorrect_words(words);
    return false;
  }
  //----------------------------------------------------------------------------------------------------
  
  
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::deinit() {
    m_wallet->removeObserver(this);
    m_node->removeObserver(static_cast<INodeObserver*>(this));
    m_node->removeObserver(static_cast<INodeRpcProxyObserver*>(this));
  
    if (!m_wallet.get())
      return true;
  
    return close_wallet();
  }
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) {
    m_wallet_file_arg = command_line::get_arg(vm, arg_wallet_file);
    m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet);
    m_daemon_address = command_line::get_arg(vm, arg_daemon_address);
    m_daemon_host = command_line::get_arg(vm, arg_daemon_host);
    m_daemon_port = command_line::get_arg(vm, arg_daemon_port);
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password) {
    m_wallet_file = wallet_file;
  
    m_wallet.reset(new WalletLegacy(m_currency, *m_node, logManager));
    m_node->addObserver(static_cast<INodeObserver*>(this));
    m_wallet->addObserver(this);
    try {
      m_initResultPromise.reset(new std::promise<std::error_code>());
      std::future<std::error_code> f_initError = m_initResultPromise->get_future();
      m_wallet->initAndGenerate(password);
      auto initError = f_initError.get();
      m_initResultPromise.reset(nullptr);
      if (initError) {
        fail_msg_writer() << "failed to generate new wallet: " << initError.message();
        return false;
      }
  
      try {
        CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
      } catch (std::exception& e) {
        fail_msg_writer() << "failed to save new wallet: " << e.what();
        throw;
      }
  
      AccountKeys keys;
      m_wallet->getAccountKeys(keys);
  
      std::string secretKeysData = std::string(reinterpret_cast<char*>(&keys.spendSecretKey), sizeof(keys.spendSecretKey)) + std::string(reinterpret_cast<char*>(&keys.viewSecretKey), sizeof(keys.viewSecretKey));
      std::string guiKeys = Tools::Base58::encode_addr(CryptoNote::parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, secretKeysData);
  
      logger(INFO, BRIGHT_GREEN) << "fango-wallet-cli is an open-source, client-side, free wallet which allows you to send & receive Fango instantly on the blockchain. You are in control of your funds & your private keys. When you generate a new wallet, login, send, receive or deposit $XFG - everything happens locally. Your seed is never transmitted, received or stored. That's why it is IMPERATIVE that you write, print or save your seed somewhere safe. The backup of keys is your responsibility only. If you lose your seed, your account can NOT be recovered. You must truly act as your own bank." << std::endl << std::endl;
  
      std::cout << "Wallet Address: " << m_wallet->getAddress() << std::endl;
      std::cout << "Private spend key: " << Common::podToHex(keys.spendSecretKey) << std::endl;
      std::cout << "Private view key: " <<  Common::podToHex(keys.viewSecretKey) << std::endl;
      std::cout << "Mnemonic Seed: " << generate_mnemonic(keys.spendSecretKey) << std::endl;
  
    }
    catch (const std::exception& e) {
      fail_msg_writer() << "failed to generate new wallet: " << e.what();
      return false;
    }
  
    success_msg_writer() <<
      "**********************************************************************\n" <<
      "Your wallet has been generated.\n" <<
      "Use \"help\" command to see the list of available commands.\n" <<
      "Always use \"exit\" command when closing fango-wallet-cli to save\n" <<
      "current session's state. Otherwise, you will possibly need to synchronize \n" <<
      "your wallet again. Your wallet keys are not under risk in doing so.\n" <<
      "**********************************************************************";
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::new_wallet(Crypto::SecretKey &secret_key, Crypto::SecretKey &view_key, const std::string &wallet_file, const std::string& password) {
                  m_wallet_file = wallet_file;
  
                  m_wallet.reset(new WalletLegacy(m_currency, *m_node.get(), logManager));
                  m_node->addObserver(static_cast<INodeObserver*>(this));
                  m_wallet->addObserver(this);
                  try {
                    m_initResultPromise.reset(new std::promise<std::error_code>());
                    std::future<std::error_code> f_initError = m_initResultPromise->get_future();
  
                    AccountKeys wallet_keys;
                    wallet_keys.spendSecretKey = secret_key;
                    wallet_keys.viewSecretKey = view_key;
                    Crypto::secret_key_to_public_key(wallet_keys.spendSecretKey, wallet_keys.address.spendPublicKey);
                    Crypto::secret_key_to_public_key(wallet_keys.viewSecretKey, wallet_keys.address.viewPublicKey);
  
                    m_wallet->initWithKeys(wallet_keys, password);
                    auto initError = f_initError.get();
                    m_initResultPromise.reset(nullptr);
                    if (initError) {
                      fail_msg_writer() << "failed to generate new wallet: " << initError.message();
                      return false;
                    }
  
                    try {
                      CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
                    } catch (std::exception& e) {
                      fail_msg_writer() << "failed to save new wallet: " << e.what();
                      throw;
                    }
  
                    AccountKeys keys;
                    m_wallet->getAccountKeys(keys);
  
                    logger(INFO, BRIGHT_WHITE) <<
                      "Imported wallet: " << m_wallet->getAddress() << std::endl;
                  }
                  catch (const std::exception& e) {
                    fail_msg_writer() << "failed to import wallet: " << e.what();
                    return false;
                  }
  
                  success_msg_writer() <<
                    "**********************************************************************\n" <<
                    "Your wallet has been imported.\n" <<
                    "Use \"help\" command to see the list of available commands.\n" <<
                    "Always use \"exit\" command when closing fango-wallet-cli to save\n" <<
                    "current session's state. Otherwise, you will possibly need to synchronize \n" <<
                    "your wallet again. Your wallet key is not under risk in doing so.\n" <<
                    "**********************************************************************";
                  return true;
                  }
  
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::close_wallet()
  {
    try {
      CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
    } catch (const std::exception& e) {
      fail_msg_writer() << e.what();
      return false;
    }
  
    m_wallet->removeObserver(this);
    m_wallet->shutdown();
  
    return true;
  }
  
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::save(const std::vector<std::string> &args)
  {
    try {
      CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
      success_msg_writer() << "Wallet data saved";
    } catch (const std::exception& e) {
      fail_msg_writer() << e.what();
    }
  
    return true;
  }
  
  bool simple_wallet::reset(const std::vector<std::string> &args) {
    {
      std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
      m_walletSynchronized = false;
    }
  
    m_wallet->reset();
    success_msg_writer(true) << "Reset completed successfully.";
  
    std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
    while (!m_walletSynchronized) {
      m_walletSynchronizedCV.wait(lock);
    }
  
    std::cout << std::endl;
  
    return true;
  }
  
  bool simple_wallet::start_mining(const std::vector<std::string>& args) {
    COMMAND_RPC_START_MINING::request req;
    req.miner_address = m_wallet->getAddress();
  
    bool ok = true;
    size_t max_mining_threads_count = (std::max)(std::thread::hardware_concurrency(), static_cast<unsigned>(2));
    if (0 == args.size()) {
      req.threads_count = 1;
    } else if (1 == args.size()) {
      uint16_t num = 1;
      ok = Common::fromString(args[0], num);
      ok = ok && (1 <= num && num <= max_mining_threads_count);
      req.threads_count = num;
    } else {
      ok = false;
    }
  
    if (!ok) {
      fail_msg_writer() << "invalid arguments. Please use start_mining [<number_of_threads>], " <<
        "<number_of_threads> should be from 1 to " << max_mining_threads_count;
      return true;
    }
  
  
    COMMAND_RPC_START_MINING::response res;
  
    try {
      HttpClient httpClient(m_dispatcher, m_daemon_host, m_daemon_port);
  
      invokeJsonCommand(httpClient, "/start_mining", req, res);
  
      std::string err = interpret_rpc_response(true, res.status);
      if (err.empty())
        success_msg_writer() << "Mining started in daemon";
      else
        fail_msg_writer() << "mining has NOT been started: " << err;
  
    } catch (const ConnectException&) {
      printConnectionError();
    } catch (const std::exception& e) {
      fail_msg_writer() << "Failed to invoke rpc method: " << e.what();
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::stop_mining(const std::vector<std::string>& args)
  {
    COMMAND_RPC_STOP_MINING::request req;
    COMMAND_RPC_STOP_MINING::response res;
  
    try {
      HttpClient httpClient(m_dispatcher, m_daemon_host, m_daemon_port);
  
      invokeJsonCommand(httpClient, "/stop_mining", req, res);
      std::string err = interpret_rpc_response(true, res.status);
      if (err.empty())
        success_msg_writer() << "Mining stopped in daemon";
      else
        fail_msg_writer() << "mining has NOT been stopped: " << err;
    } catch (const ConnectException&) {
      printConnectionError();
    } catch (const std::exception& e) {
      fail_msg_writer() << "Failed to invoke rpc method: " << e.what();
    }
  
    return true;
  }
  
  bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
  {
  	if (args.size() != 1 && args.size() != 2) {
  		fail_msg_writer() << "Usage: get_reserve_proof (all|<amount>) [<message>]";
  		return true;
  	}
  
  
  	uint64_t reserve = 0;
  	if (args[0] != "all") {
  		if (!m_currency.parseAmount(args[0], reserve)) {
  			fail_msg_writer() << "amount is wrong: " << args[0];
  			return true;
  		}
  	} else {
  		reserve = m_wallet->actualBalance();
  	}
  
  	try {
  		const std::string sig_str = m_wallet->getReserveProof(reserve, args.size() == 2 ? args[1] : "");
  		
  		//logger(INFO, BRIGHT_WHITE) << "\n\n" << sig_str << "\n\n" << std::endl;
  
  		const std::string filename = "reserve_proof_" + args[0] + "_XFG.txt";
  		boost::system::error_code ec;
  		if (boost::filesystem::exists(filename, ec)) {
  			boost::filesystem::remove(filename, ec);
  		}
  
  		std::ofstream proofFile(filename, std::ios::out | std::ios::trunc | std::ios::binary);
  		if (!proofFile.good()) {
  			return false;
  		}
  		proofFile << sig_str;
  
  		success_msg_writer() << "signature file saved to: " << filename;
  
  	} catch (const std::exception &e) {
  		fail_msg_writer() << e.what();
  	}
  
  	return true;
  }
  
  
  bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
  {
    if(args.size() != 2 && args.size() != 3) {
      fail_msg_writer() << "Usage: get_tx_proof <txid> <dest_address> [<txkey>]";
      return true;
    }
  
    const std::string &str_hash = args[0];
    Crypto::Hash txid;
    if (!parse_hash256(str_hash, txid)) {
      fail_msg_writer() << "Failed to parse txid";
      return true;
    }
  
    const std::string address_string = args[1];
    CryptoNote::AccountPublicAddress address;
    if (!m_currency.parseAccountAddressString(address_string, address)) {
       fail_msg_writer() << "Failed to parse address " << address_string;
       return true;
    }
  
    std::string sig_str;
    Crypto::SecretKey tx_key, tx_key2;
    bool r = m_wallet->get_tx_key(txid, tx_key);
  
    if (args.size() == 3) {
      Crypto::Hash tx_key_hash;
      size_t size;
      if (!Common::fromHex(args[2], &tx_key_hash, sizeof(tx_key_hash), size) || size != sizeof(tx_key_hash)) {
        fail_msg_writer() << "failed to parse tx_key";
        return true;
      }
      tx_key2 = *(struct Crypto::SecretKey *) &tx_key_hash;
  
      if (r) {
        if (args.size() == 3 && tx_key != tx_key2) {
          fail_msg_writer() << "Tx secret key was found for the given txid, but you've also provided another tx secret key which doesn't match the found one.";
          return true;
        }
      }
  	tx_key = tx_key2;
    } else {
      if (!r) {
        fail_msg_writer() << "Tx secret key wasn't found in the wallet file. Provide it as the optional third parameter if you have it elsewhere.";
        return true;
      }
    }
  
    if (m_wallet->getTxProof(txid, address, tx_key, sig_str)) {
      success_msg_writer() << "Signature: " << sig_str << std::endl;
    }
  
    return true;
  }
  
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::initCompleted(std::error_code result) {
    if (m_initResultPromise.get() != nullptr) {
      m_initResultPromise->set_value(result);
    }
  }
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::connectionStatusUpdated(bool connected) {
    if (connected) {
      logger(INFO, GREEN) << "Wallet connected to daemon.";
    } else {
      printConnectionError();
    }
  }
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::externalTransactionCreated(CryptoNote::TransactionId transactionId)  {
    WalletLegacyTransaction txInfo;
    m_wallet->getTransaction(transactionId, txInfo);
  
    std::stringstream logPrefix;
    if (txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
      logPrefix << "Unconfirmed";
    } else {
      logPrefix << "Height " << txInfo.blockHeight << ',';
    }
  
    if (txInfo.totalAmount >= 0) {
      logger(INFO, GREEN) <<
        logPrefix.str() << " transaction " << Common::podToHex(txInfo.hash) <<
        ", received " << m_currency.formatAmount(txInfo.totalAmount);
    } else {
      logger(INFO, MAGENTA) <<
        logPrefix.str() << " transaction " << Common::podToHex(txInfo.hash) <<
        ", spent " << m_currency.formatAmount(static_cast<uint64_t>(-txInfo.totalAmount));
    }
  
    if (txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
      m_refresh_progress_reporter.update(m_node->getLastLocalBlockHeight(), true);
    } else {
      m_refresh_progress_reporter.update(txInfo.blockHeight, true);
    }
  }
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::synchronizationCompleted(std::error_code result) {
    std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
    m_walletSynchronized = true;
    m_walletSynchronizedCV.notify_one();
  }
  
  void simple_wallet::synchronizationProgressUpdated(uint32_t current, uint32_t total) {
    std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
    if (!m_walletSynchronized) {
      m_refresh_progress_reporter.update(current, false);
    }
  }
  
  bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/) {
    success_msg_writer() << "available balance: " << m_currency.formatAmount(m_wallet->actualBalance()) <<
      ", locked amount: " << m_currency.formatAmount(m_wallet->pendingBalance());
  
    return true;
  }
  
  bool simple_wallet::sign_message(const std::vector<std::string>& args)
  {
    if(args.size() < 1)
    {
      fail_msg_writer() << "Use: sign_message <message>";
      return true;
    }
      
    AccountKeys keys;
    m_wallet->getAccountKeys(keys);
  
    Crypto::Hash message_hash;
    Crypto::Signature sig;
    Crypto::cn_fast_hash(args[0].data(), args[0].size(), message_hash);
    Crypto::generate_signature(message_hash, keys.address.spendPublicKey, keys.spendSecretKey, sig);
    
    success_msg_writer() << "Sig" << Tools::Base58::encode(std::string(reinterpret_cast<char*>(&sig)));
  
    return true;	
  }
  
  bool simple_wallet::verify_signature(const std::vector<std::string>& args)
  {
    if (args.size() != 3)
    {
      fail_msg_writer() << "Use: verify_signature <message> <address> <signature>";
      return true;
    }
    
    std::string encodedSig = args[2];
    const size_t prefix_size = strlen("Sig");
    
    if(encodedSig.substr(0, prefix_size) != "Sig")
    {
      fail_msg_writer() << "Invalid signature prefix";
      return true;
    } 
    
    Crypto::Hash message_hash;
    Crypto::cn_fast_hash(args[0].data(), args[0].size(), message_hash);
    
    std::string decodedSig;
    Crypto::Signature sig;
    Tools::Base58::decode(encodedSig.substr(prefix_size), decodedSig);
    memcpy(&sig, decodedSig.data(), sizeof(sig));
    
    uint64_t prefix;
    CryptoNote::AccountPublicAddress addr;
    CryptoNote::parseAccountAddressString(prefix, addr, args[1]);
    
    if(Crypto::check_signature(message_hash, addr.spendPublicKey, sig))
      success_msg_writer() << "Valid";
    else
      success_msg_writer() << "Invalid";
    return true;
  }
  
  /* ------------------------------------------------------------------------------------------- */
  
  /* CREATE INTEGRATED ADDRESS */
  /* take a payment Id as an argument and generate an integrated wallet address */
  
  bool simple_wallet::create_integrated(const std::vector<std::string>& args/* = std::vector<std::string>()*/) 
  {
  
    /* check if there is a payment id */
    if (args.empty()) 
    {
  
      fail_msg_writer() << "Please enter a payment ID";
      return true;
    }
  
    std::string paymentID = args[0];
    std::regex hexChars("^[0-9a-f]+$");
    if(paymentID.size() != 64 || !regex_match(paymentID, hexChars))
    {
      fail_msg_writer() << "Invalid payment ID";
      return true;
    }
  
    std::string address = m_wallet->getAddress();
    uint64_t prefix;
    CryptoNote::AccountPublicAddress addr;
  
    /* get the spend and view public keys from the address */
    if(!CryptoNote::parseAccountAddressString(prefix, addr, address))
    {
      logger(ERROR, BRIGHT_RED) << "Failed to parse account address from string";
      return true;
    }
  
    CryptoNote::BinaryArray ba;
    CryptoNote::toBinaryArray(addr, ba);
    std::string keys = Common::asString(ba);
  
    /* create the integrated address the same way you make a public address */
    std::string integratedAddress = Tools::Base58::encode_addr (CryptoNote::parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX,
                                                                paymentID + keys
    );
  
    std::cout << std::endl << "Integrated address: " << integratedAddress << std::endl << std::endl;
  
    return true;
  }
  
  /* ---------------------------------------------------------------------------------------- */
  
  
  bool simple_wallet::export_keys(const std::vector<std::string>& args/* = std::vector<std::string>()*/) {
    AccountKeys keys;
    m_wallet->getAccountKeys(keys);
  
    std::string secretKeysData = std::string(reinterpret_cast<char*>(&keys.spendSecretKey), sizeof(keys.spendSecretKey)) + std::string(reinterpret_cast<char*>(&keys.viewSecretKey), sizeof(keys.viewSecretKey));
    std::string guiKeys = Tools::Base58::encode_addr(CryptoNote::parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, secretKeysData);
  
    logger(INFO, BRIGHT_GREEN) << std::endl << "fango-wallet-cli is an open-source, client-side, free wallet which allows you to send & receive Fango instantly on the blockchain. You are in control of your funds & your private keys. When you generate a new wallet, login, send, receive or deposit $XFG - everything happens locally. Your seed is never transmitted, received or stored. That's why it is imperative to write, print or save your seed somewhere safe. The backup of keys is your responsibility only. If you lose your seed, your account can not be recovered. You must truly act as your own bank." << std::endl << std::endl;
  
    std::cout << "Private spend key: " << Common::podToHex(keys.spendSecretKey) << std::endl;
    std::cout << "Private view key: " <<  Common::podToHex(keys.viewSecretKey) << std::endl;
  
    Crypto::PublicKey unused_dummy_variable;
    Crypto::SecretKey deterministic_private_view_key;
  
    AccountBase::generateViewFromSpend(keys.spendSecretKey, deterministic_private_view_key, unused_dummy_variable);
  
    bool deterministic_private_keys = deterministic_private_view_key == keys.viewSecretKey;
  
  /* dont show a mnemonic seed if it is an old non-deterministic wallet */
    if (deterministic_private_keys) {
      std::cout << "Mnemonic seed: " << generate_mnemonic(keys.spendSecretKey) << std::endl << std::endl;
    }
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args) {
    bool hasTransfers = false;
    size_t transactionsCount = m_wallet->getTransactionCount();
    for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) {
      WalletLegacyTransaction txInfo;
      m_wallet->getTransaction(trantransactionNumber, txInfo);
      if (txInfo.totalAmount < 0) continue;
      hasTransfers = true;
      logger(INFO) << "        amount       \t                              tx id";
      logger(INFO, GREEN) <<
        std::setw(21) << m_currency.formatAmount(txInfo.totalAmount) << '\t' << Common::podToHex(txInfo.hash);
    }
  
    if (!hasTransfers) success_msg_writer() << "No incoming transfers";
    return true;
  }
  
  bool simple_wallet::listTransfers(const std::vector<std::string>& args) {
    bool haveTransfers = false;
    bool haveBlockHeight = false;
    std::string blockHeightString = ""; 
    uint32_t blockHeight = 0;
    WalletLegacyTransaction txInfo;
  
  
    /* get block height from arguments */
    if (args.empty()) 
    {
      haveBlockHeight = false;
    } else {
      blockHeightString = args[0];
      haveBlockHeight = true;
      blockHeight = atoi(blockHeightString.c_str());
    }
  
    size_t transactionsCount = m_wallet->getTransactionCount();
    for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) 
    {
      
      m_wallet->getTransaction(trantransactionNumber, txInfo);
      if (txInfo.state != WalletLegacyTransactionState::Active || txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
        continue;
      }
  
      if (!haveTransfers) {
        printListTransfersHeader(logger);
        haveTransfers = true;
      }
  
      if (haveBlockHeight == false) {
        printListTransfersItem(logger, txInfo, *m_wallet, m_currency);
      } else {
        if (txInfo.blockHeight >= blockHeight) {
          printListTransfersItem(logger, txInfo, *m_wallet, m_currency);
  
        }
  
      }
    }
  
    if (!haveTransfers) {
      success_msg_writer() << "No transfers";
    }
  
    return true;
  }
  
  bool simple_wallet::show_payments(const std::vector<std::string> &args) {
    if (args.empty()) {
      fail_msg_writer() << "expected at least one payment ID";
      return true;
    }
  
    try {
      auto hashes = args;
      std::sort(std::begin(hashes), std::end(hashes));
      hashes.erase(std::unique(std::begin(hashes), std::end(hashes)), std::end(hashes));
      std::vector<PaymentId> paymentIds;
      paymentIds.reserve(hashes.size());
      std::transform(std::begin(hashes), std::end(hashes), std::back_inserter(paymentIds), [](const std::string& arg) {
        PaymentId paymentId;
        if (!CryptoNote::parsePaymentId(arg, paymentId)) {
          throw std::runtime_error("payment ID has invalid format: \"" + arg + "\", expected 64-character string");
        }
  
        return paymentId;
      });
  
      logger(INFO) << "                            payment                             \t" <<
        "                          transaction                           \t" <<
        "  height\t       amount        ";
  
      auto payments = m_wallet->getTransactionsByPaymentIds(paymentIds);
  
      for (auto& payment : payments) {
        for (auto& transaction : payment.transactions) {
          success_msg_writer(true) <<
            Common::podToHex(payment.paymentId) << '\t' <<
            Common::podToHex(transaction.hash) << '\t' <<
            std::setw(8) << transaction.blockHeight << '\t' <<
            std::setw(21) << m_currency.formatAmount(transaction.totalAmount);
        }
  
        if (payment.transactions.empty()) {
          success_msg_writer() << "No payments with id " << Common::podToHex(payment.paymentId);
        }
      }
    } catch (std::exception& e) {
      fail_msg_writer() << "show_payments exception: " << e.what();
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args) {
    try {
      uint64_t bc_height = m_node->getLastLocalBlockHeight();
      success_msg_writer() << bc_height;
    } catch (std::exception &e) {
      fail_msg_writer() << "failed to get blockchain height: " << e.what();
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::show_num_unlocked_outputs(const std::vector<std::string>& args) {
    try {
      std::vector<TransactionOutputInformation> unlocked_outputs = m_wallet->getUnspentOutputs();
      success_msg_writer() << "Count: " << unlocked_outputs.size();
      for (const auto& out : unlocked_outputs) {
        success_msg_writer() << "Key: " << out.transactionPublicKey << " amount: " << m_currency.formatAmount(out.amount);
      }
    } catch (std::exception &e) {
      fail_msg_writer() << "failed to get outputs: " << e.what();
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::optimize_outputs(const std::vector<std::string>& args) {
    try {
      CryptoNote::WalletHelper::SendCompleteResultObserver sent;
      WalletHelper::IWalletRemoveObserverGuard removeGuard(*m_wallet, sent);
  
      std::vector<CryptoNote::WalletLegacyTransfer> transfers;
      std::vector<CryptoNote::TransactionMessage> messages;
      std::string extraString;
      uint64_t fee = CryptoNote::parameters::MINIMUM_FEE_V2;
      uint64_t mixIn = 0;
      uint64_t unlockTimestamp = 0;
      uint64_t ttl = 0;
      Crypto::SecretKey transactionSK;
      CryptoNote::TransactionId tx = m_wallet->sendTransaction(transactionSK, transfers, fee, extraString, mixIn, unlockTimestamp, messages, ttl);
      if (tx == WALLET_LEGACY_INVALID_TRANSACTION_ID) {
        fail_msg_writer() << "Can't send money";
        return true;
      }
  
      std::error_code sendError = sent.wait(tx);
      removeGuard.removeObserver();
  
      if (sendError) {
        fail_msg_writer() << sendError.message();
        return true;
      }
  
      CryptoNote::WalletLegacyTransaction txInfo;
      m_wallet->getTransaction(tx, txInfo);
      success_msg_writer(true) << "Money successfully sent, transaction " << Common::podToHex(txInfo.hash);
      success_msg_writer(true) << "Transaction secret key " << Common::podToHex(transactionSK);
  
      try {
        CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
      } catch (const std::exception& e) {
        fail_msg_writer() << e.what();
        return true;
      }
    } catch (const std::system_error& e) {
      fail_msg_writer() << e.what();
    } catch (const std::exception& e) {
      fail_msg_writer() << e.what();
    } catch (...) {
      fail_msg_writer() << "unknown error";
    }
  
    return true;
  }
  
  //----------------------------------------------------------------------------------------------------
  
  
  bool simple_wallet::optimize_all_outputs(const std::vector<std::string>& args) {
  
    uint64_t num_unlocked_outputs = 0;
  
    try {
      num_unlocked_outputs = m_wallet->getNumUnlockedOutputs();
      success_msg_writer() << "Total outputs: " << num_unlocked_outputs;
  
    } catch (std::exception &e) {
      fail_msg_writer() << "failed to get outputs: " << e.what();
    }
  
    uint64_t remainder = num_unlocked_outputs % 100;
    uint64_t rounds = (num_unlocked_outputs - remainder) / 100;
    success_msg_writer() << "Total optimization rounds: " << rounds;
    for(uint64_t a = 1; a < rounds; a = a + 1 ) {
      
      try {
        CryptoNote::WalletHelper::SendCompleteResultObserver sent;
        WalletHelper::IWalletRemoveObserverGuard removeGuard(*m_wallet, sent);
  
        std::vector<CryptoNote::WalletLegacyTransfer> transfers;
        std::vector<CryptoNote::TransactionMessage> messages;
        std::string extraString;
        uint64_t fee = CryptoNote::parameters::MINIMUM_FEE_V2;
        uint64_t mixIn = 0;
        uint64_t unlockTimestamp = 0;
        uint64_t ttl = 0;
        Crypto::SecretKey transactionSK;
        CryptoNote::TransactionId tx = m_wallet->sendTransaction(transactionSK, transfers, fee, extraString, mixIn, unlockTimestamp, messages, ttl);
        if (tx == WALLET_LEGACY_INVALID_TRANSACTION_ID) {
          fail_msg_writer() << "Can't send money";
          return true;
        }
  
        std::error_code sendError = sent.wait(tx);
        removeGuard.removeObserver();
  
        if (sendError) {
          fail_msg_writer() << sendError.message();
          return true;
        }
  
        CryptoNote::WalletLegacyTransaction txInfo;
        m_wallet->getTransaction(tx, txInfo);
        success_msg_writer(true) << a << ". Optimization transaction successfully sent, transaction " << Common::podToHex(txInfo.hash);
  
        try {
          CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
        } catch (const std::exception& e) {
          fail_msg_writer() << e.what();
          return true;
        }
      } catch (const std::system_error& e) {
        fail_msg_writer() << e.what();
      } catch (const std::exception& e) {
        fail_msg_writer() << e.what();
      } catch (...) {
        fail_msg_writer() << "unknown error";
      }
    }
    return true;
  }
  
  //----------------------------------------------------------------------------------------------------
  
  std::string simple_wallet::resolveAlias(const std::string& aliasUrl) {
    std::string host;
  	std::string uri;
  	std::vector<std::string>records;
  	std::string address;
  
  	if (!Common::fetch_dns_txt(aliasUrl, records)) {
  		throw std::runtime_error("Failed to lookup DNS record");
  	}
  
  	for (const auto& record : records) {
  		if (processServerAliasResponse(record, address)) {
  			return address;
  		}
  	}
  	throw std::runtime_error("Failed to parse server response");
  }
  //----------------------------------------------------------------------------------------------------
  
  /* This extracts the fee address from the remote node */
  std::string simple_wallet::getFeeAddress() {
    
    HttpClient httpClient(m_dispatcher, m_daemon_host, m_daemon_port);
  
    HttpRequest req;
    HttpResponse res;
  
    req.setUrl("/feeaddress");
    try {
  	  httpClient.request(req, res);
    }
    catch (const std::exception& e) {
  	  fail_msg_writer() << "Error connecting to the remote node: " << e.what();
    }
  
    if (res.getStatus() != HttpResponse::STATUS_200) {
  	  fail_msg_writer() << "Remote node returned code " + std::to_string(res.getStatus());
    }
  
    std::string address;
    if (!processServerFeeAddressResponse(res.getBody(), address)) {
  	  fail_msg_writer() << "Failed to parse remote node response";
    }
  
    return address;
  }
  
  
  bool simple_wallet::transfer(const std::vector<std::string> &args) {
    try {
      TransferCommand cmd(m_currency);
  
      if (!cmd.parseArguments(logger, args))
        return true;
  
      for (auto& kv: cmd.aliases) {
        std::string address;
  
        try {
          address = resolveAlias(kv.first);
  
          AccountPublicAddress ignore;
          if (!m_currency.parseAccountAddressString(address, ignore)) {
            throw std::runtime_error("Address \"" + address + "\" is invalid");
          }
        } catch (std::exception& e) {
          fail_msg_writer() << "Couldn't resolve alias: " << e.what() << ", alias: " << kv.first;
          return true;
        }
  
        for (auto& transfer: kv.second) {
          transfer.address = address;
        }
      }
  
      if (!cmd.aliases.empty()) {
        if (!askAliasesTransfersConfirmation(cmd.aliases, m_currency)) {
          return true;
        }
  
        for (auto& kv: cmd.aliases) {
          std::copy(std::move_iterator<std::vector<WalletLegacyTransfer>::iterator>(kv.second.begin()),
                    std::move_iterator<std::vector<WalletLegacyTransfer>::iterator>(kv.second.end()),
                    std::back_inserter(cmd.dsts));
        }
      }
  
      std::vector<TransactionMessage> messages;
      for (auto dst : cmd.dsts) {
        for (auto msg : cmd.messages) {
          messages.emplace_back(TransactionMessage{ msg, dst.address });
        }
      }
  
      uint64_t ttl = 0;
      if (cmd.ttl != 0) {
        ttl = static_cast<uint64_t>(time(nullptr)) + cmd.ttl;
      }
  
      CryptoNote::WalletHelper::SendCompleteResultObserver sent;
  
      std::string extraString;
      std::copy(cmd.extra.begin(), cmd.extra.end(), std::back_inserter(extraString));
  
      WalletHelper::IWalletRemoveObserverGuard removeGuard(*m_wallet, sent);
  
      /* set static mixin of 2*/
      cmd.fake_outs_count = CryptoNote::parameters::MINIMUM_MIXIN;
  
      /* force minimum fee */
      if (cmd.fee < CryptoNote::parameters::MINIMUM_FEE_V2) {
        cmd.fee = CryptoNote::parameters::MINIMUM_FEE_V2;
      }
  
      Crypto::SecretKey transactionSK;
      CryptoNote::TransactionId tx = m_wallet->sendTransaction(transactionSK, cmd.dsts, cmd.fee, extraString, cmd.fake_outs_count, 0, messages, ttl);
      if (tx == WALLET_LEGACY_INVALID_TRANSACTION_ID) {
        fail_msg_writer() << "Can't send money";
        return true;
      }
  
      std::error_code sendError = sent.wait(tx);
      removeGuard.removeObserver();
  
      if (sendError) {
        fail_msg_writer() << sendError.message();
        return true;
      }
  
      CryptoNote::WalletLegacyTransaction txInfo;
      m_wallet->getTransaction(tx, txInfo);
      success_msg_writer(true) << "XFG successfully sent, transaction hash: " << Common::podToHex(txInfo.hash);
      success_msg_writer(true) << "Transaction secret key " << Common::podToHex(transactionSK); 
  
      try {
        CryptoNote::WalletHelper::storeWallet(*m_wallet, m_wallet_file);
      } catch (const std::exception& e) {
        fail_msg_writer() << e.what();
        return true;
      }
    } catch (const std::system_error& e) {
      fail_msg_writer() << e.what();
    } catch (const std::exception& e) {
      fail_msg_writer() << e.what();
    } catch (...) {
      fail_msg_writer() << "unknown error";
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::run() {
    {
      std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
      while (!m_walletSynchronized) {
        m_walletSynchronizedCV.wait(lock);
      }
    }
  
    std::cout << std::endl;
  
    std::string addr_start = m_wallet->getAddress().substr(0, 6);
    m_consoleHandler.start(false, "[wallet " + addr_start + "]: ", Common::Console::Color::BrightYellow);
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  void simple_wallet::stop() {
    m_consoleHandler.requestStop();
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) {
    success_msg_writer() << m_wallet->getAddress();
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool simple_wallet::process_command(const std::vector<std::string> &args) {
    return m_consoleHandler.runCommand(args);
  }
  
  void simple_wallet::printConnectionError() const {
    fail_msg_writer() << "wallet failed to connect to daemon (" << m_daemon_address << ").";
  }
  
  
  int main(int argc, char* argv[]) {
  #ifdef _WIN32
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
  #endif
  
    po::options_description desc_general("General options");
    command_line::add_arg(desc_general, command_line::arg_help);
    command_line::add_arg(desc_general, command_line::arg_version);
  
    po::options_description desc_params("Wallet options");
    command_line::add_arg(desc_params, arg_wallet_file);
    command_line::add_arg(desc_params, arg_generate_new_wallet);
    command_line::add_arg(desc_params, arg_password);
    command_line::add_arg(desc_params, arg_daemon_address);
    command_line::add_arg(desc_params, arg_daemon_host);
    command_line::add_arg(desc_params, arg_daemon_port);
    command_line::add_arg(desc_params, arg_command);
    command_line::add_arg(desc_params, arg_log_level);
    command_line::add_arg(desc_params, arg_testnet);
    Tools::wallet_rpc_server::init_options(desc_params);
  
    po::positional_options_description positional_options;
    positional_options.add(arg_command.name, -1);
  
    po::options_description desc_all;
    desc_all.add(desc_general).add(desc_params);
  
    Logging::LoggerManager logManager;
    Logging::LoggerRef logger(logManager, "simplewallet");
    System::Dispatcher dispatcher;
  
    po::variables_map vm;
  
    bool r = command_line::handle_error_helper(desc_all, [&]() {
      po::store(command_line::parse_command_line(argc, argv, desc_general, true), vm);
  
      if (command_line::get_arg(vm, command_line::arg_help)) {
        CryptoNote::Currency tmp_currency = CryptoNote::CurrencyBuilder(logManager).currency();
        CryptoNote::simple_wallet tmp_wallet(dispatcher, tmp_currency, logManager);
  
        std::cout << "fango-wallet-cli -" << PROJECT_VERSION_LONG << std::endl;
        std::cout << "Usage: fango-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [--daemon-address=<host>:<port>] [<COMMAND>]";
        std::cout << desc_all << '\n' << tmp_wallet.get_commands_str();
        return false;
      } else if (command_line::get_arg(vm, command_line::arg_version))  {
        std::cout << "fango-wallet-cli -" << PROJECT_VERSION_LONG << std::endl;
        return false;
      }
  
      auto parser = po::command_line_parser(argc, argv).options(desc_params).positional(positional_options);
      po::store(parser.run(), vm);
      po::notify(vm);
      return true;
    });
  
    if (!r)
      return 1;
  
    //set up logging options
    Level logLevel = DEBUGGING;
  
    if (command_line::has_arg(vm, arg_log_level)) {
      logLevel = static_cast<Level>(command_line::get_arg(vm, arg_log_level));
    }
  
    logManager.configure(buildLoggerConfiguration(logLevel, Common::ReplaceExtenstion(argv[0], ".log")));
  
    logger(INFO, BRIGHT_GREEN) << "fango-wallet-cli -" << PROJECT_VERSION_LONG;
  
    CryptoNote::Currency currency = CryptoNote::CurrencyBuilder(logManager).
      testnet(command_line::get_arg(vm, arg_testnet)).currency();
  
    if (command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_bind_port)) {
      //runs wallet with rpc interface
      if (!command_line::has_arg(vm, arg_wallet_file)) {
        logger(ERROR, BRIGHT_RED) << "Wallet file not set.";
        return 1;
      }
  
      if (!command_line::has_arg(vm, arg_daemon_address)) {
        logger(ERROR, BRIGHT_RED) << "Daemon address not set.";
        return 1;
      }
  
      if (!command_line::has_arg(vm, arg_password)) {
        logger(ERROR, BRIGHT_RED) << "Wallet password not set.";
        return 1;
      }
  
      std::string wallet_file = command_line::get_arg(vm, arg_wallet_file);
      std::string wallet_password = command_line::get_arg(vm, arg_password);
      std::string daemon_address = command_line::get_arg(vm, arg_daemon_address);
      std::string daemon_host = command_line::get_arg(vm, arg_daemon_host);
      uint16_t daemon_port = command_line::get_arg(vm, arg_daemon_port);
      if (daemon_host.empty())
        daemon_host = "localhost";
      if (!daemon_port)
        daemon_port = RPC_DEFAULT_PORT;
  
      if (!daemon_address.empty()) {
        if (!parseUrlAddress(daemon_address, daemon_host, daemon_port)) {
          logger(ERROR, BRIGHT_RED) << "failed to parse daemon address: " << daemon_address;
          return 1;
        }
      }
  
      std::unique_ptr<INode> node(new NodeRpcProxy(daemon_host, daemon_port));
  
      std::promise<std::error_code> errorPromise;
      std::future<std::error_code> error = errorPromise.get_future();
      auto callback = [&errorPromise](std::error_code e) {errorPromise.set_value(e); };
      node->init(callback);
      if (error.get()) {
        logger(ERROR, BRIGHT_RED) << ("failed to init NodeRPCProxy");
        return 1;
      }
  
      std::unique_ptr<IWalletLegacy> wallet(new WalletLegacy(currency, *node.get(), logManager));
  
      std::string walletFileName;
      try  {
        walletFileName = ::tryToOpenWalletOrLoadKeysOrThrow(logger, wallet, wallet_file, wallet_password);
  
        logger(INFO) << "available balance: " << currency.formatAmount(wallet->actualBalance()) <<
        ", locked amount: " << currency.formatAmount(wallet->pendingBalance());
  
        logger(INFO, BRIGHT_GREEN) << "Loaded ok";
      } catch (const std::exception& e)  {
        logger(ERROR, BRIGHT_RED) << "Wallet initialize failed: " << e.what();
        return 1;
      }
  
      Tools::wallet_rpc_server wrpc(dispatcher, logManager, *wallet, *node, currency, walletFileName);
  
      if (!wrpc.init(vm)) {
        logger(ERROR, BRIGHT_RED) << "Failed to initialize wallet rpc server";
        return 1;
      }
  
      Tools::SignalHandler::install([&wrpc, &wallet] {
        wrpc.send_stop_signal();
      });
  
      logger(INFO) << "Starting wallet rpc server";
      wrpc.run();
      logger(INFO) << "Stopped wallet rpc server";
  
      try {
        logger(INFO) << "Storing wallet...";
        CryptoNote::WalletHelper::storeWallet(*wallet, walletFileName);
        logger(INFO, BRIGHT_GREEN) << "Stored ok";
      } catch (const std::exception& e) {
        logger(ERROR, BRIGHT_RED) << "Failed to store wallet: " << e.what();
        return 1;
      }
    } else {
      //runs wallet with console interface
      CryptoNote::simple_wallet wal(dispatcher, currency, logManager);
  
      if (!wal.init(vm)) {
        logger(ERROR, BRIGHT_RED) << "Failed to initialize wallet";
        return 1;
      }
  
      std::vector<std::string> command = command_line::get_arg(vm, arg_command);
      if (!command.empty())
        wal.process_command(command);
  
      Tools::SignalHandler::install([&wal] {
        wal.stop();
      });
  
      wal.run();
  
      if (!wal.deinit()) {
        logger(ERROR, BRIGHT_RED) << "Failed to close wallet";
      } else {
        logger(INFO) << "Wallet closed";
      }
    }
    return 1;
    //CATCH_ENTRY_L0("main", 1);
  }
  