text: // Copyright (c) 2012-2017, The CryptoNote developers, The Bytecoin developers
  //
  // This file is part of Bytecoin.
  //
  // Bytecoin is free software: you can redistribute it and/or modify
  // it under the terms of the GNU Lesser General Public License as published by
  // the Free Software Foundation, either version 3 of the License, or
  // (at your option) any later version.
  //
  // Bytecoin is 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.  See the
  // GNU Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public License
  // along with Bytecoin.  If not, see <http://www.gnu.org/licenses/>.
  
  #include "PoolWallet.h"
  
  #include <ctime>
  #include <fstream>
  #include <future>
  #include <iomanip>
  #include <thread>
  #include <set>
  #include <sstream>
  
  #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/CommandLine.h"
  #include "Common/SignalHandler.h"
  #include "Common/StringTools.h"
  #include "Common/PathTools.h"
  #include "Common/Util.h"
  #include "CryptoNoteCore/CryptoNoteFormatUtils.h"
  #include "CryptoNoteProtocol/CryptoNoteProtocolHandler.h"
  #include "Mnemonics/electrum-words.cpp"
  #include "NodeRpcProxy/NodeRpcProxy.h"
  #include "Rpc/CoreRpcServerCommandsDefinitions.h"
  #include "Rpc/HttpClient.h"
  
  #include "Wallet/WalletRpcServer.h"
  #include "WalletLegacy/WalletLegacy.h"
  #include "Wallet/LegacyKeysImporter.h"
  #include "WalletLegacy/WalletHelper.h"
  
  #include "version.h"
  
  #include <Logging/LoggerManager.h>
  
  #if defined(WIN32)
  #include <crtdbg.h>
  #include <io.h>
  #else
  #include <unistd.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 11898", 0 };
  const command_line::arg_descriptor<uint32_t> arg_log_level = { "set_log", "", INFO, true };
  const command_line::arg_descriptor<bool>      arg_SYNC_FROM_ZERO  = {"SYNC_FROM_ZERO", "Sync from block 0. Use for premine wallet or brainwallet", false};
  const command_line::arg_descriptor<bool>      arg_exit_after_generate  = {"exit-after-generate", "Exit immediately after generating a wallet, do not try to sync with the daemon", false};
  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", "" };
  const command_line::arg_descriptor<std::string> arg_restore_view = { "restore-view-key", "Specify the View Key to re-generate an existing wallet", ""};
  const command_line::arg_descriptor<std::string> arg_restore_spend = { "restore-spend-key", "Specify the Spend Key to re-generate an existing wallet", ""};
  
  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;
  }
  
  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", "%D %T %L ");
  
    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");
    header += makeCenteredString(BLOCK_MAX_WIDTH, "confs");
  
    logger(INFO) << header;
    logger(INFO) << std::string(header.size(), '-');
  }
  
  void printListTransfersItem(LoggerRef& logger, const WalletLegacyTransaction& txInfo, IWalletLegacy& wallet, const Currency& currency, uint64_t currentHeight) {
    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");
    }
    uint64_t confirmations = currentHeight - txInfo.blockHeight;
  
    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
      << "  " << std::setw(BLOCK_MAX_WIDTH) << confirmations;
  
    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;
  }
  
  }
  
  std::string pool_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 pool_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) {
    success_msg_writer() << get_commands_str();
    return true;
  }
  
  bool pool_wallet::exit(const std::vector<std::string> &args) {
    m_consoleHandler.requestStop();
    return true;
  }
  
  /* Wait for input so users can read errors before the window closes if they
     launch from a GUI rather than a terminal */
  /* Not a member of pool wallet because has to be called from main, could
     make static I suppose */
  void pause_for_input(int argc) {
    /* if they passed arguments they're probably in a terminal so the errors will
       stay visible */
    if (argc == 1) {
      #if defined(WIN32)
      if (_isatty(_fileno(stdout)) && _isatty(_fileno(stdin))) {
      #else
      if(isatty(fileno(stdout)) && isatty(fileno(stdin))) {
      #endif
        std::cout << "Press any key to close the program: ";
        getchar();
      }
    }
  }
  
  pool_wallet::pool_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, "poolwallet"),
    m_refresh_progress_reporter(*this),
    m_initResultPromise(nullptr),
    m_walletSynchronized(false) {
    //m_consoleHandler.setHandler("start_mining", boost::bind(&pool_wallet::start_mining, this, _1), "start_mining [<number_of_threads>] - Start mining in daemon");
    //m_consoleHandler.setHandler("stop_mining", boost::bind(&pool_wallet::stop_mining, this, _1), "Stop mining in daemon");
    //m_consoleHandler.setHandler("refresh", boost::bind(&pool_wallet::refresh, this, _1), "Resynchronize transactions and balance");
    m_consoleHandler.setHandler("export_keys", boost::bind(&pool_wallet::export_keys, this, _1), "Show the secret keys of the opened wallet");
    m_consoleHandler.setHandler("balance", boost::bind(&pool_wallet::show_balance, this, _1), "Show current wallet balance");
    m_consoleHandler.setHandler("incoming_transfers", boost::bind(&pool_wallet::show_incoming_transfers, this, _1), "Show incoming transfers");
    m_consoleHandler.setHandler("outgoing_transfers", boost::bind(&pool_wallet::show_outgoing_transfers, this, _1), "Show outgoing transfers");
    m_consoleHandler.setHandler("list_transfers", boost::bind(&pool_wallet::listTransfers, this, _1), "Show all known transfers");
    m_consoleHandler.setHandler("payments", boost::bind(&pool_wallet::show_payments, this, _1), "payments <payment_id_1> [<payment_id_2> ... <payment_id_N>] - Show payments <payment_id_1>, ... <payment_id_N>");
    m_consoleHandler.setHandler("bc_height", boost::bind(&pool_wallet::show_blockchain_height, this, _1), "Show blockchain height");
    m_consoleHandler.setHandler("transfer", boost::bind(&pool_wallet::transfer, this, _1),
      "transfer <mixin_count> <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [-p payment_id] [-f fee]"
      " - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. "
      "<mixin_count> is the number of transactions yours is indistinguishable from (from 0 to maximum available)");
    m_consoleHandler.setHandler("set_log", boost::bind(&pool_wallet::set_log, this, _1), "set_log <level> - Change current log level, <level> is a number 0-4");
    m_consoleHandler.setHandler("address", boost::bind(&pool_wallet::print_address, this, _1), "Show current wallet public address");
    m_consoleHandler.setHandler("view_tx_outputs", boost::bind(&pool_wallet::print_outputs_from_transaction, this, _1), "view_tx_outputs <transaction_hash> - Find outputs that belong to you in a transaction");
    m_consoleHandler.setHandler("save", boost::bind(&pool_wallet::save, this, _1), "Save wallet synchronized data");
    m_consoleHandler.setHandler("reset", boost::bind(&pool_wallet::reset, this, _1), "Discard cache data and start synchronizing from the start");
    m_consoleHandler.setHandler("help", boost::bind(&pool_wallet::help, this, _1), "Show this help");
    m_consoleHandler.setHandler("exit", boost::bind(&pool_wallet::exit, this, _1), "Close wallet");
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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 pool_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;
    }
  
    bool restore_cmd = (!m_restore_view.empty() && !m_restore_spend.empty());
  
    bool key_import = true;
  
    if (m_generate_new.empty() && m_wallet_file_arg.empty()) {
  
      std::cout << std::endl << "Welcome, please choose an option below:"
                << std::endl 
                << std::endl << "\t[G] - Generate a new wallet address"
                << std::endl << "\t[O] - Open a wallet already on your system"
                << std::endl << "\t[S] - Regenerate your wallet using a seed phrase of words"
                << std::endl << "\t[I] - Import your wallet using a View Key and Spend Key"
                << std::endl << std::endl << "or, press CTRL_C to exit: "
                << std::flush;
  
      char c;
      
      do {
        std::string answer;
        std::getline(std::cin, answer);
        c = answer[0];
        c = std::tolower(c);
        if (!(c == 'o' || c == 'g' || c == 'i' || c == 's')) {
          std::cout << "Unknown command: " << answer << std::endl;
        } else {
          break;
        }
      } while (true);
  
      if (c == 'e') {
        return false;
      }
  
      std::cout << "Specify wallet file name (e.g., wallet.bin).\n";
      std::string userInput;
  
      bool validInput = true;
  
      do {
        if (c == 'o') {
          std::cout << "Enter the name of the wallet you wish to open: ";
        } else {
          std::cout << "What do you want to call your new wallet?: ";
        }
  
        std::getline(std::cin, userInput);
        boost::algorithm::trim(userInput);
        
        if (c != 'o') {
          std::string ignoredString;
          std::string walletFileName;
  
          WalletHelper::prepareFileNames(userInput, ignoredString, walletFileName);
  
          boost::system::error_code ignore;
  
          if (boost::filesystem::exists(walletFileName, ignore)) {
            std::cout << walletFileName << " already exists! Try a different name." << std::endl;
            validInput = false;
          } else {
            validInput = true;
          }
        }
      } while (!validInput);
  
      if (c == 'i') {
        key_import = true;
        m_import_new = userInput;
      } else if (c == 's') {
        key_import = false;
        m_import_new = userInput;
      } else if (c == 'g') {
        m_generate_new = userInput;
      } else {
        m_wallet_file_arg = userInput;
      }
    }
  
    if (restore_cmd && !m_wallet_file_arg.empty()) {
      // we are restoring a wallet from the view/spend keys
      m_import_new = m_wallet_file_arg;
    }
  
    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;
    sync_from_zero = command_line::get_arg(vm, arg_SYNC_FROM_ZERO);
    if (sync_from_zero) {
      sync_from_height = 0;
    }
    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;
      }
    }
  
    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;
      }
    } else {
      m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port);
    }
  
    if (command_line::has_arg(vm, arg_password)) {
      m_pwd_container.password(command_line::get_arg(vm, arg_password));
    } else if (!m_pwd_container.read_password(!m_generate_new.empty() || !m_import_new.empty())) {
      fail_msg_writer() << "failed to read wallet password";
      return false;
    }
  
    this->m_node.reset(new NodeRpcProxy(m_daemon_host, m_daemon_port, logger.getLogger()));
  
    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;
    }
    
    sync_from_zero = command_line::get_arg(vm, arg_SYNC_FROM_ZERO);
    if (sync_from_zero) {
      sync_from_height = 0;
    }
    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, m_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 (m_restore_view.empty() || m_restore_spend.empty()) {
        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 << "Mnemonic 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);
        }
  
      } else {
  
        // the view/spend keys have been specified
  
        private_view_key_string = m_restore_view;
        private_spend_key_string = m_restore_spend;
  
        boost::algorithm::trim(private_view_key_string);
        boost::algorithm::trim(private_spend_key_string);
  
      }
  
      /* 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, m_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(!exit_after_generate) {
        m_wallet.reset(new WalletLegacy(m_currency, *m_node));
        m_wallet->syncAll(sync_from_zero, 0);
      }
      try {
        m_wallet_file = tryToOpenWalletOrLoadKeysOrThrow(logger, m_wallet, m_wallet_file_arg, m_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" <<
        "**********************************************************************";
  
      if(exit_after_generate) {
        m_consoleHandler.requestStop();
        std::exit(0);
      }
  
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  /* Be careful using this function. It does generate a mnemonic seed
  perfectly well from a private spend key, however, poolwallet previously
  generated a random spend and view key, which were unrelated. With this set of
  commits, this has been changed, to make the view key derived from the spend
  key. This is done by running keccak-256 on the input, and then using that as
  a seed to create a set of private and public keys, the spend private and public
  keys.
  
  With this implemented, users only need to save a private spend key, or the
  mnemonic seed, which is a nice way of representing the spend key. However,
  we don't want users with the old way of generating keys thinking they only need
  to keep their mnemonic seed, because this will only generate their private
  spend key, and NOT their private view key - and they won't be able to restore
  their funds.
  */
  std::string pool_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) << "\nCant create the mnemonic for the private spend key!";
    }
  
    return mnemonic_str;
  }
  //----------------------------------------------------------------------------------------------------
  void pool_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 pool_wallet::is_valid_mnemonic(std::string &mnemonic_phrase, Crypto::SecretKey &private_spend_key) {
  
    /* Uncommenting these will allow importing of different languages, exporting
       in different languages however has not been added, as it will require
       changing the export_keys command to take an argument to specify what
       language the seed should be exported in. For now, multilanguage support
       has been disabled as there are a couple of issues - we can't print out
       what words aren't present in the dictionary if we don't know what
       dictionary they are using, and it's a lot more friendly to work that
       out automatically rather than asking, and secondly, it is possible that
       dictionaries of other words can overlap enough to allow an esperanto
       seed for example to be imported as an english seed */
  
    //static std::string languages[] = {"English", "Nederlands", "Français", "Português", "Italiano", "Deutsch", "русский язык", "简体中文 (中国)", "Esperanto", "Lojban"};
    static std::string languages[] = {"English"};
    
    //static const int num_of_languages = 10;
    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;
    }
  
    /* Check every language for our phrase so the user doesn't have to specify
       it, this shouldn't be an issue as long as one language doesn't have enough
       of another languages words, might need some testing */
    for (int i = 0; i < num_of_languages; i++) {
      if (crypto::ElectrumWords::words_to_bytes(mnemonic_phrase, private_spend_key, languages[i])) {
        return true;
      }
    }
  
    /* The issue with this is if we try and automagically determine what language
       the seed phrase is in, then we can't log words which aren't in the x
       dictionary, we will have to take an argument to know what language they
       are in, but this is less user friendly. */
    logger(ERROR, BRIGHT_RED) << "Invalid mnemonic phrase!";
    log_incorrect_words(words);
    return false;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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 pool_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);
    exit_after_generate = command_line::get_arg(vm, arg_exit_after_generate);
    m_restore_view = command_line::get_arg(vm, arg_restore_view);
    m_restore_spend = command_line::get_arg(vm, arg_restore_spend);
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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.get()));
    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->syncAll(sync_from_zero, 0);
      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::cout << "\nWelcome to your new wallet, here is your payment address:\n";
      Common::Console::setTextColor(Common::Console::Color::BrightGreen);
      std::cout << m_wallet->getAddress();
      Common::Console::setTextColor(Common::Console::Color::Default);
      std::cout << "\n\nPlease copy your secret keys and mnemonic seed and store them in a secure location:";
      Common::Console::setTextColor(Common::Console::Color::BrightGreen);
      std::cout <<
  	"\nspend key: " << Common::podToHex(keys.spendSecretKey) <<
  	"\nview key: " << Common::podToHex(keys.viewSecretKey) <<
          "\nmnemonic seed:" << generate_mnemonic(keys.spendSecretKey);
      Common::Console::setTextColor(Common::Console::Color::BrightRed);
      std::cout << "\n\nIf you lose these your wallet cannot be recreated!\n\n";
      Common::Console::setTextColor(Common::Console::Color::Default);
      std::cout <<
        "**********************************************************************\n" <<
        "Use \"help\" command to see the list of available commands.\n" <<
        "Always use \"exit\" command when closing poolwallet 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 anyway.\n" <<
        "**********************************************************************\n";
  
    } catch (const std::exception& e) {
      fail_msg_writer() << "failed to generate new wallet: " << e.what();
      return false;
    }
  
    if(exit_after_generate) {
      m_consoleHandler.requestStop();
      std::exit(0);
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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()));
    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 poolwallet 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 anyway.\n" <<
      "**********************************************************************";
  
      if(exit_after_generate) {
        m_consoleHandler.requestStop();
        std::exit(0);
      }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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 pool_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 pool_wallet::reset(const std::vector<std::string> &args) {
    {
      std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
      m_walletSynchronized = false;
    }
  
    if(0 == args.size()) {
      success_msg_writer(true) << "Resetting wallet from block height 0";
      m_wallet->syncAll(true, 0);
      m_wallet->reset();
    } else {
      uint64_t height = 0;
      bool ok = Common::fromString(args[0], height);
      if (ok) {
        success_msg_writer(true) << "Resetting wallet from block height " << height;
        m_wallet->syncAll(true, height);
        m_wallet->reset(height);
      }
    }
  
  
    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 pool_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 pool_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;
  }
  //----------------------------------------------------------------------------------------------------
  void pool_wallet::initCompleted(std::error_code result) {
    if (m_initResultPromise.get() != nullptr) {
      m_initResultPromise->set_value(result);
    }
  }
  //----------------------------------------------------------------------------------------------------
  void pool_wallet::connectionStatusUpdated(bool connected) {
    if (connected) {
      logger(INFO, GREEN) << "Wallet connected to daemon.";
    } else {
      printConnectionError();
    }
  }
  //----------------------------------------------------------------------------------------------------
  void pool_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 pool_wallet::synchronizationCompleted(std::error_code result) {
    std::unique_lock<std::mutex> lock(m_walletSynchronizedMutex);
    m_walletSynchronized = true;
    m_walletSynchronizedCV.notify_one();
  }
  
  void pool_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 pool_wallet::export_keys(const std::vector<std::string>& args) {
    AccountKeys keys;
    m_wallet->getAccountKeys(keys);
  
    /* The console handler fucks up input, so lets pause it whilst we're
      getting input then unpause afterwards */
    m_consoleHandler.pause();
  
    if (!m_pwd_container.read_and_validate()) {
      std::cout << "Incorrect password!" << std::endl;
      m_consoleHandler.unpause();
      return false;
    }
  
    m_consoleHandler.unpause();
  
    // output the keys directly to the console to avoid them being output to the logfile.
    std::cout << "Spend secret key: " << Common::podToHex(keys.spendSecretKey) << std::endl;
    std::cout << "View secret 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;
  
    /* Only output the mnemonic seed if it's valid for this wallet - the old
      wallet code generated random spend and view keys so we can't create a 
      mnemonic key */
    if (deterministic_private_keys) {
      std::cout << "Mnemonic seed: " << generate_mnemonic(keys.spendSecretKey) << std::endl;
    }
  
    return true;
  }
  
  bool pool_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()) <<
      ", total amount: " << m_currency.formatAmount(m_wallet->actualBalance() + m_wallet->pendingBalance());
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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) <<  // spent - magenta
        std::setw(21) << m_currency.formatAmount(txInfo.totalAmount) << '\t' << Common::podToHex(txInfo.hash);
    }
  
    if (!hasTransfers) success_msg_writer() << "No incoming transfers";
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_wallet::show_outgoing_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, MAGENTA) <<
        std::setw(21) << m_currency.formatAmount(txInfo.totalAmount) << '\t' << Common::podToHex(txInfo.hash);
    }
  
    if (!hasTransfers) success_msg_writer() << "No outgoing transfers";
    return true;
  }
  
  bool pool_wallet::listTransfers(const std::vector<std::string>& args) {
    bool haveTransfers = false;
  
    size_t transactionsCount = m_wallet->getTransactionCount();
    for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) {
      WalletLegacyTransaction txInfo;
      m_wallet->getTransaction(trantransactionNumber, txInfo);
      if (txInfo.state != WalletLegacyTransactionState::Active || txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
        continue;
      }
  
      if (!haveTransfers) {
        printListTransfersHeader(logger);
        haveTransfers = true;
      }
      printListTransfersItem(logger, txInfo, *m_wallet, m_currency, m_node->getLastLocalBlockHeight());
    }
  
    if (!haveTransfers) {
      success_msg_writer() << "No transfers";
    }
  
    return true;
  }
  
  bool pool_wallet::show_payments(const std::vector<std::string> &args) {
    if (args.empty()) {
      fail_msg_writer() << "expected at least one payment ID";
      return true;
    }
  
    logger(INFO) << "                            payment                             \t" <<
      "                          transaction                           \t" <<
      "  height\t       amount        ";
  
    bool payments_found = false;
    for (const std::string& arg: args) {
      Crypto::Hash expectedPaymentId;
      if (CryptoNote::parsePaymentId(arg, expectedPaymentId)) {
        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;
          std::vector<uint8_t> extraVec;
          extraVec.reserve(txInfo.extra.size());
          std::for_each(txInfo.extra.begin(), txInfo.extra.end(), [&extraVec](const char el) { extraVec.push_back(el); });
  
          Crypto::Hash paymentId;
          if (CryptoNote::getPaymentIdFromTxExtra(extraVec, paymentId) && paymentId == expectedPaymentId) {
            payments_found = true;
            success_msg_writer(true) <<
              paymentId << "\t\t" <<
              Common::podToHex(txInfo.hash) <<
              std::setw(8) << txInfo.blockHeight << '\t' <<
              std::setw(21) << m_currency.formatAmount(txInfo.totalAmount);// << '\t' <<
          }
        }
  
        if (!payments_found) {
          success_msg_writer() << "No payments with id " << expectedPaymentId;
          continue;
        }
      } else {
        fail_msg_writer() << "payment ID has invalid format: \"" << arg << "\", expected 64-character string";
      }
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_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 pool_wallet::confirmTransaction(TransferCommand cmd, bool multiAddress) {
    std::string feeString;
  
    /* 10 shells = 0.1 TRTL */
    if (cmd.fee == 10) {
      feeString = "0.1 TRTL (minimum)";
    } else {
      feeString = m_currency.formatAmount(cmd.fee) + " TRTL";
    }
  
    std::string walletName = boost::filesystem::change_extension(m_wallet_file, "").string();
  
    std::cout << std::endl << "Confirm Transaction?" << std::endl;
  
    if (!multiAddress) {
      std::cout << "You are sending " << m_currency.formatAmount(cmd.dsts[0].amount)
                << " TRTL, with a fee of " << feeString << std::endl
                << "FROM: " << walletName << std::endl
                << "TO: " << std::endl << cmd.dsts[0].address << std::endl
                << std::endl;
    } else {
      std::cout << "You are sending a transaction to "
                << std::to_string(cmd.dsts.size())
                << " addresses, with a combined fee of " << feeString
                << std::endl << std::endl;
  
      for (auto destination : cmd.dsts) {
        std::cout << "You are sending " << m_currency.formatAmount(destination.amount)
                  << " TRTL" << std::endl << "FROM: " << walletName << std::endl
                  << "TO: " << std::endl << destination.address << std::endl
                  << std::endl;
      }
    }
  
    while (true) {
      std::cout << "Is this correct? (Y/N): " << std::flush;
  
      /* The console handler fucks up input, so lets pause it whilst we're
         getting input then unpause afterwards */
      m_consoleHandler.pause();
  
      std::string answer;
      std::getline(std::cin, answer);
  
      char c = std::tolower(answer[0]);
  
      if (c == 'y') {
        if (!m_pwd_container.read_and_validate()) {
          std::cout << "Incorrect password!" << std::endl;
          continue;
        }
  
        return true;
  
      } else if (c == 'n') {
        return false;
      /* Don't loop forever on EOF */
      } else if (c == std::ifstream::traits_type::eof()) {
        return false;
      } else {
        std::cout << "Bad input: " << answer << " - please enter either Y or N.";
      }
    }
  
    /* Because the compiler is dumb */
    return false;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_wallet::transfer(const std::vector<std::string> &args) {
    try {
      TransferCommand cmd(m_currency);
  
      if (!cmd.parseArguments(logger, args))
        return false;
      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);
  
      bool proceed = confirmTransaction(cmd, cmd.dsts.size() > 1);
  
      /* Restore prompt and input handling */
      m_consoleHandler.unpause();
  
      if (!proceed) {
        std::cout << "Cancelling transaction." << std::endl;
        return true;
      }
  
      CryptoNote::TransactionId tx = m_wallet->sendTransaction(cmd.dsts, cmd.fee, extraString, cmd.fake_outs_count, 0);
      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);
      std::cout << "Transaction has been sent! ID:" << std::endl
                << Common::podToHex(txInfo.hash) << std::endl;
  
      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 pool_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 pool_wallet::stop() {
    m_consoleHandler.requestStop();
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_wallet::print_outputs_from_transaction(const std::vector<std::string>& args)
  {
    if (args.size() == 0)
    {
      logger(ERROR, BRIGHT_RED) << "Must supply transaction hash as argument!";
      return false;
    }
  
    Crypto::Hash transactionHash;
    std::string transactionHashString = args[0];
    boost::algorithm::trim(transactionHashString);
    size_t size;
    
    if (!Common::fromHex(transactionHashString, &transactionHash, sizeof(transactionHash), size))
    {
      logger(ERROR, BRIGHT_RED) << "Failed to parse - please ensure you entered the hash correctly.";
      return false;
    }
  
    std::vector<TransactionDetails> transactions;
    std::vector<Crypto::Hash> transactionHashes;
    transactionHashes.push_back(transactionHash);
  
    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->getTransactions(transactionHashes, transactions, callback);
    auto error = f_error.get();
  
    if (error)
    {
      logger(ERROR, BRIGHT_RED) << "Failed to find transaction hash! Ensure you entered it correctly and your daemon is fully synced.";
      return false;
    }
  
    TransactionDetails ourTransaction = transactions[0];
  
    AccountKeys keys;
    m_wallet->getAccountKeys(keys);
  
    Crypto::SecretKey privateViewKey = keys.viewSecretKey;
    Crypto::SecretKey privateSpendKey = keys.spendSecretKey;
  
    /* The transaction public key */
    Crypto::PublicKey publicTransactionKey = ourTransaction.extra.publicKey;
  
    /* The users public spend key */
    Crypto::PublicKey publicSpendKey;
    Crypto::secret_key_to_public_key(privateSpendKey, publicSpendKey);
  
    /* Derived key is created from the users private view key and the public
       transaction key */
    Crypto::KeyDerivation derivation;
    Crypto::generate_key_derivation(publicTransactionKey, privateViewKey, derivation);
  
    Crypto::PublicKey outputPublicKey;
  
    bool found = false;
    uint64_t sum = 0;
  
    /* Loop through each output in the transaction */
    for (size_t i = 0; i < ourTransaction.outputs.size(); ++i)
    {
      Crypto::derive_public_key(derivation, i, publicSpendKey, outputPublicKey);
  
      TransactionOutputTarget target = ourTransaction.outputs[i].output.target;
  
      KeyOutput targetPubKey = boost::get<CryptoNote::KeyOutput>(target);
  
      if (targetPubKey.key == outputPublicKey)
      {
        uint64_t amount = ourTransaction.outputs[i].output.amount; 
  
        std::string trtl = m_currency.formatAmount(amount);
  
        sum += amount;
  
        found = true;
  
        logger(INFO, GREEN) << "The transaction output of " << trtl << " TRTL belongs to you!";
      }
    }
  
    if (!found)
    {
      logger(ERROR, BRIGHT_RED) << "No outputs were found that belong to you, searched " << ourTransaction.outputs.size() << " outputs.";
    }
    else
    {
      std::string trtl = m_currency.formatAmount(sum);
  
      logger(INFO, GREEN) << "Outputs totalling " << trtl << " TRTL were sent to your wallet!";
    }
  
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) {
    success_msg_writer() << m_wallet->getAddress();
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  bool pool_wallet::process_command(const std::vector<std::string> &args) {
    return m_consoleHandler.runCommand(args);
  }
  
  void pool_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_restore_spend);
    command_line::add_arg(desc_params, arg_restore_view);
    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);
    command_line::add_arg(desc_params, arg_SYNC_FROM_ZERO);
    command_line::add_arg(desc_params, arg_exit_after_generate);
  
    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, "poolwallet");
    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::pool_wallet tmp_wallet(dispatcher, tmp_currency, logManager);
  
        std::cout << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG << std::endl;
        std::cout << "Usage: poolwallet [--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 << CRYPTONOTE_NAME << " wallet v" << 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")));
  
    std::cout << "TurtleCoin v" << PROJECT_VERSION << " Poolwallet" << std::endl;
  
    std::cout << "Please note that usage of simplewallet/poolwallet has been "
              << "deprecated for pool usage." << std::endl
              << "If you are using turtle-pool, you can trivially transfer to "
              << "walletd by following these instructions:" << std::endl
              << "https://github.com/turtlecoin/turtle-pool/pull/5" << std::endl;
  
    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 the rpc interface is run, ensure that either legacy mode or an RPC
  	  password is set.
  	*/
  
  	if (!command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_password) &&
  		!command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_legacy_security)) {
  	  logger(ERROR, BRIGHT_RED) << "Required RPC password is not set.";
  	  return 1;
  	}
  
      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, logger.getLogger()));
  
      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()));
  
      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::pool_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);
  }
  
  
  