aboutsummaryrefslogtreecommitdiff
path: root/src/simplewallet/simplewallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/simplewallet/simplewallet.cpp')
-rw-r--r--src/simplewallet/simplewallet.cpp486
1 files changed, 406 insertions, 80 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 27bc05a2e..1773172f2 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -43,6 +43,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/regex.hpp>
+#include <boost/range/adaptor/transformed.hpp>
#include "include_base_utils.h"
#include "common/i18n.h"
#include "common/command_line.h"
@@ -780,10 +781,7 @@ bool simple_wallet::payment_id(const std::vector<std::string> &args/* = std::vec
bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (!try_connect_to_daemon())
- {
- fail_msg_writer() << tr("Cannot connect to daemon");
return true;
- }
const bool per_byte = m_wallet->use_fork_rules(HF_VERSION_PER_BYTE_FEE);
const uint64_t base_fee = m_wallet->get_base_fee();
const char *base = per_byte ? "byte" : "kB";
@@ -1801,6 +1799,27 @@ bool simple_wallet::version(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func)
+{
+ std::vector<std::string> tx_aux;
+
+ message_writer(console_color_white, false) << tr("Please confirm the transaction on the device");
+
+ m_wallet->cold_sign_tx(ptx_vector, exported_txs, dsts_info, tx_aux);
+
+ if (accept_func && !accept_func(exported_txs))
+ {
+ MERROR("Transactions rejected by callback");
+ return false;
+ }
+
+ // aux info
+ m_wallet->cold_tx_aux_import(exported_txs.ptx, tx_aux);
+
+ // import key images
+ return m_wallet->import_key_images(exported_txs.key_images);
+}
+
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
@@ -2253,6 +2272,33 @@ bool simple_wallet::set_ignore_fractional_outputs(const std::vector<std::string>
return true;
}
+bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ if (args.size() == 0){
+ fail_msg_writer() << tr("Device name not specified");
+ return true;
+ }
+
+ m_wallet->device_name(args[0]);
+ bool r = false;
+ try {
+ r = m_wallet->reconnect_device();
+ if (!r){
+ fail_msg_writer() << tr("Device reconnect failed");
+ }
+
+ } catch(const std::exception & e){
+ MWARNING("Device reconnect failed: " << e.what());
+ fail_msg_writer() << tr("Device reconnect failed: ") << e.what();
+ }
+
+ }
+ return true;
+}
+
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if(args.empty())
@@ -2493,12 +2539,17 @@ simple_wallet::simple_wallet()
"Pending or Failed: \"failed\"|\"pending\", \"out\", Time, Amount*, Transaction Hash, Payment ID, Fee, Input addresses**, \"-\", Note\n\n"
"* Excluding change and fee.\n"
"** Set of address indices used as inputs in this transfer."));
+ m_cmd_binder.set_handler("export_transfers",
+ boost::bind(&simple_wallet::export_transfers, this, _1),
+ tr("export_transfers [in|out|all|pending|failed|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<filepath>]"),
+ tr("Export to CSV the incoming/outgoing transfers within an optional height range."));
m_cmd_binder.set_handler("unspent_outputs",
boost::bind(&simple_wallet::unspent_outputs, this, _1),
tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"),
tr("Show the unspent outputs of a specified address within an optional amount range."));
m_cmd_binder.set_handler("rescan_bc",
boost::bind(&simple_wallet::rescan_blockchain, this, _1),
+ tr("rescan_bc [hard]"),
tr("Rescan the blockchain from scratch, losing any information which can not be recovered from the blockchain itself."));
m_cmd_binder.set_handler("set_tx_note",
boost::bind(&simple_wallet::set_tx_note, this, _1),
@@ -2537,6 +2588,10 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::import_key_images, this, _1),
tr("import_key_images <file>"),
tr("Import a signed key images list and verify their spent status."));
+ m_cmd_binder.set_handler("hw_key_images_sync",
+ boost::bind(&simple_wallet::hw_key_images_sync, this, _1),
+ tr("hw_key_images_sync"),
+ tr("Synchronizes key images with the hw wallet."));
m_cmd_binder.set_handler("hw_reconnect",
boost::bind(&simple_wallet::hw_reconnect, this, _1),
tr("hw_reconnect"),
@@ -2728,6 +2783,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("subaddress-lookahead", set_subaddress_lookahead, tr("<major>:<minor>"));
CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer"));
CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1"));
+ CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>"));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
@@ -4245,15 +4301,15 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char
return pwd_container->password();
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init)
+bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bool is_init)
{
if (!try_connect_to_daemon(is_init))
return true;
LOCK_IDLE_SCOPE();
- if (reset)
- m_wallet->rescan_blockchain(false);
+ if (reset != ResetNone)
+ m_wallet->rescan_blockchain(reset == ResetHard, false);
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
@@ -4332,7 +4388,7 @@ bool simple_wallet::refresh(const std::vector<std::string>& args)
start_height = 0;
}
}
- return refresh_main(start_height, false);
+ return refresh_main(start_height, ResetNone);
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::show_balance_unlocked(bool detailed)
@@ -4430,7 +4486,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
tools::wallet2::transfer_container transfers;
m_wallet->get_transfers(transfers);
- bool transfers_found = false;
+ size_t transfers_found = 0;
for (const auto& td : transfers)
{
if (!filter || available != td.m_spent)
@@ -4443,7 +4499,6 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
if (verbose)
verbose_string = (boost::format("%68s%68s") % tr("pubkey") % tr("key image")).str();
message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%16s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % tr("addr index") % verbose_string;
- transfers_found = true;
}
std::string verbose_string;
if (verbose)
@@ -4458,6 +4513,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
td.m_txid %
td.m_subaddr_index.minor %
verbose_string;
+ ++transfers_found;
}
}
@@ -4476,6 +4532,10 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
success_msg_writer() << tr("No incoming unavailable transfers");
}
}
+ else
+ {
+ success_msg_writer() << boost::format("Found %u/%u transfers") % transfers_found % transfers.size();
+ }
return true;
}
@@ -4608,10 +4668,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
{
uint32_t version;
if (!try_connect_to_daemon(false, &version))
- {
- fail_msg_writer() << tr("failed to connect to the daemon");
return false;
- }
// available for RPC version 1.4 or higher
if (version < MAKE_CORE_RPC_VERSION(1, 4))
return true;
@@ -4834,12 +4891,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
local_args.pop_back();
}
+ vector<cryptonote::address_parse_info> dsts_info;
vector<cryptonote::tx_destination_entry> dsts;
size_t num_subaddresses = 0;
for (size_t i = 0; i < local_args.size(); )
{
+ dsts_info.emplace_back();
+ cryptonote::address_parse_info & info = dsts_info.back();
cryptonote::tx_destination_entry de;
- cryptonote::address_parse_info info;
bool r = true;
// check for a URI
@@ -5123,6 +5182,28 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
+ else if (m_wallet->get_account().get_device().has_tx_cold_sign())
+ {
+ try
+ {
+ tools::wallet2::signed_tx_set signed_tx;
+ if (!cold_sign_tx(ptx_vector, signed_tx, dsts_info, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); })){
+ fail_msg_writer() << tr("Failed to cold sign transaction with HW wallet");
+ return true;
+ }
+
+ commit_or_save(signed_tx.ptx, m_do_not_relay);
+ }
+ catch (const std::exception& e)
+ {
+ handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon());
+ }
+ catch (...)
+ {
+ LOG_ERROR("Unknown error");
+ fail_msg_writer() << tr("unknown error");
+ }
+ }
else if (m_wallet->watch_only())
{
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
@@ -5545,6 +5626,31 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
+ else if (m_wallet->get_account().get_device().has_tx_cold_sign())
+ {
+ try
+ {
+ tools::wallet2::signed_tx_set signed_tx;
+ std::vector<cryptonote::address_parse_info> dsts_info;
+ dsts_info.push_back(info);
+
+ if (!cold_sign_tx(ptx_vector, signed_tx, dsts_info, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); })){
+ fail_msg_writer() << tr("Failed to cold sign transaction with HW wallet");
+ return true;
+ }
+
+ commit_or_save(signed_tx.ptx, m_do_not_relay);
+ }
+ catch (const std::exception& e)
+ {
+ handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon());
+ }
+ catch (...)
+ {
+ LOG_ERROR("Unknown error");
+ fail_msg_writer() << tr("unknown error");
+ }
+ }
else if (m_wallet->watch_only())
{
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
@@ -6003,8 +6109,8 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs)
{
std::string extra_message;
- if (!txs.transfers.empty())
- extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.size()).str();
+ if (!txs.transfers.second.empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.second.size()).str();
return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}, extra_message);
}
//----------------------------------------------------------------------------------------------------
@@ -6451,10 +6557,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args)
}
if (!try_connect_to_daemon())
- {
- fail_msg_writer() << tr("failed to connect to the daemon");
return true;
- }
SCOPED_WALLET_UNLOCK();
@@ -6489,10 +6592,7 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args)
}
if (!try_connect_to_daemon())
- {
- fail_msg_writer() << tr("failed to connect to the daemon");
return true;
- }
std::string sig_str;
if (!epee::file_io_utils::load_file_to_string(args[1], sig_str))
@@ -6546,10 +6646,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
}
if (!try_connect_to_daemon())
- {
- fail_msg_writer() << tr("failed to connect to the daemon");
return true;
- }
SCOPED_WALLET_UNLOCK();
@@ -6577,10 +6674,7 @@ bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args)
}
if (!try_connect_to_daemon())
- {
- fail_msg_writer() << tr("failed to connect to the daemon");
return true;
- }
cryptonote::address_parse_info info;
if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[0], oa_prompter))
@@ -6656,9 +6750,9 @@ static std::string get_human_readable_timespan(std::chrono::seconds seconds)
return sw::tr("a long time");
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
+// mutates local_args as it parses and consumes arguments
+bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vector<transfer_view>& transfers)
{
- std::vector<std::string> local_args = args_;
bool in = true;
bool out = true;
bool pending = true;
@@ -6667,15 +6761,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
bool coinbase = true;
uint64_t min_height = 0;
uint64_t max_height = (uint64_t)-1;
- boost::optional<uint32_t> subaddr_index;
- if(local_args.size() > 4) {
- fail_msg_writer() << tr("usage: show_transfers [in|out|all|pending|failed|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
- return true;
- }
-
- LOCK_IDLE_SCOPE();
-
// optional in/out selector
if (local_args.size() > 0) {
if (local_args[0] == "in" || local_args[0] == "incoming") {
@@ -6713,38 +6799,34 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
{
if (!parse_subaddress_indices(local_args[0], subaddr_indices))
- return true;
+ return false;
local_args.erase(local_args.begin());
}
// min height
- if (local_args.size() > 0) {
+ if (local_args.size() > 0 && local_args[0].find('=') == std::string::npos) {
try {
min_height = boost::lexical_cast<uint64_t>(local_args[0]);
}
catch (const boost::bad_lexical_cast &) {
fail_msg_writer() << tr("bad min_height parameter:") << " " << local_args[0];
- return true;
+ return false;
}
local_args.erase(local_args.begin());
}
// max height
- if (local_args.size() > 0) {
+ if (local_args.size() > 0 && local_args[0].find('=') == std::string::npos) {
try {
max_height = boost::lexical_cast<uint64_t>(local_args[0]);
}
catch (const boost::bad_lexical_cast &) {
fail_msg_writer() << tr("bad max_height parameter:") << " " << local_args[0];
- return true;
+ return false;
}
local_args.erase(local_args.begin());
}
- std::multimap<uint64_t, std::tuple<epee::console_colors, std::string, std::string>> output;
-
- PAUSE_READLINE();
-
if (in || coinbase) {
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
m_wallet->get_payments(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices);
@@ -6756,23 +6838,26 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
+ std::string destination = m_wallet->get_subaddress_as_str({m_current_subaddress_account, pd.m_subaddr_index.minor});
const std::string type = pd.m_coinbase ? tr("block") : tr("in");
- output.insert(std::make_pair(pd.m_block_height, std::make_tuple(epee::console_color_green, type, (boost::format("%25.25s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str())));
+ const bool unlocked = m_wallet->is_tx_spendtime_unlocked(pd.m_unlock_time, pd.m_block_height);
+ transfers.push_back({
+ pd.m_block_height,
+ pd.m_timestamp,
+ type,
+ true,
+ pd.m_amount,
+ pd.m_tx_hash,
+ payment_id,
+ 0,
+ {{destination, pd.m_amount}},
+ {pd.m_subaddr_index.minor},
+ note,
+ (unlocked) ? "unlocked" : "locked"
+ });
}
}
- auto print_subaddr_indices = [](const std::set<uint32_t>& indices)
- {
- stringstream ss;
- bool first = true;
- for (uint32_t i : indices)
- {
- ss << (first ? "" : ",") << i;
- first = false;
- }
- return ss.str();
- };
-
if (out) {
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments;
m_wallet->get_payments_out(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices);
@@ -6780,27 +6865,31 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
const tools::wallet2::confirmed_transfer_details &pd = i->second;
uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
uint64_t fee = pd.m_amount_in - pd.m_amount_out;
- std::string dests;
+ std::vector<std::pair<std::string, uint64_t>> destinations;
for (const auto &d: pd.m_dests) {
- if (!dests.empty())
- dests += ", ";
- dests += get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr) + ": " + print_money(d.amount);
+ destinations.push_back({get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr), d.amount});
}
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(i->first);
- output.insert(std::make_pair(pd.m_block_height, std::make_tuple(epee::console_color_magenta, tr("out"), (boost::format("%25.25s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str())));
+ transfers.push_back({
+ pd.m_block_height,
+ pd.m_timestamp,
+ "out",
+ true,
+ pd.m_amount_in - change - fee,
+ i->first,
+ payment_id,
+ fee,
+ destinations,
+ pd.m_subaddr_indices,
+ note,
+ "-"
+ });
}
}
- // print in and out sorted by height
- for (std::multimap<uint64_t, std::tuple<epee::console_colors, std::string, std::string>>::const_iterator i = output.begin(); i != output.end(); ++i) {
- message_writer(std::get<0>(i->second), false) <<
- boost::format("%8.8llu %6.6s %s") %
- ((unsigned long long)i->first) % std::get<1>(i->second) % std::get<2>(i->second);
- }
-
if (pool) {
try
{
@@ -6816,10 +6905,24 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
+ std::string destination = m_wallet->get_subaddress_as_str({m_current_subaddress_account, pd.m_subaddr_index.minor});
std::string double_spend_note;
if (i->second.m_double_spend_seen)
double_spend_note = tr("[Double spend seen on the network: this transaction may or may not end up being mined] ");
- message_writer() << (boost::format("%8.8s %6.6s %25.25s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str();
+ transfers.push_back({
+ "pool",
+ pd.m_timestamp,
+ "in",
+ false,
+ pd.m_amount,
+ pd.m_tx_hash,
+ payment_id,
+ 0,
+ {{destination, pd.m_amount}},
+ {pd.m_subaddr_index.minor},
+ note + double_spend_note,
+ "locked"
+ });
}
}
catch (const std::exception& e)
@@ -6836,20 +6939,187 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
uint64_t amount = pd.m_amount_in;
uint64_t fee = amount - pd.m_amount_out;
+ std::vector<std::pair<std::string, uint64_t>> destinations;
+ for (const auto &d: pd.m_dests) {
+ destinations.push_back({get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr), d.amount});
+ }
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(i->first);
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
if ((failed && is_failed) || (!is_failed && pending)) {
- message_writer() << (boost::format("%8.8s %6.6s %25.25s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str();
+ transfers.push_back({
+ (is_failed ? "failed" : "pending"),
+ pd.m_timestamp,
+ "out",
+ false,
+ amount - pd.m_change - fee,
+ i->first,
+ payment_id,
+ fee,
+ destinations,
+ pd.m_subaddr_indices,
+ note,
+ "-"
+ });
+ }
+ }
+ }
+ // sort by block, then by timestamp (unconfirmed last)
+ std::sort(transfers.begin(), transfers.end(), [](const transfer_view& a, const transfer_view& b) -> bool {
+ if (a.confirmed && !b.confirmed)
+ return true;
+ if (a.block == b.block)
+ return a.timestamp < b.timestamp;
+ return a.block < b.block;
+ });
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
+{
+ std::vector<std::string> local_args = args_;
+
+ if(local_args.size() > 4) {
+ fail_msg_writer() << tr("usage: show_transfers [in|out|all|pending|failed|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
+ return true;
+ }
+
+ LOCK_IDLE_SCOPE();
+
+ std::vector<transfer_view> all_transfers;
+
+ if (!get_transfers(local_args, all_transfers))
+ return true;
+
+ PAUSE_READLINE();
+
+ for (const auto& transfer : all_transfers)
+ {
+ const auto color = transfer.confirmed ? ((transfer.direction == "in" || transfer.direction == "block") ? console_color_green : console_color_magenta) : console_color_white;
+
+ std::string destinations = "-";
+ if (!transfer.outputs.empty())
+ {
+ destinations = "";
+ for (const auto& output : transfer.outputs)
+ {
+ if (!destinations.empty())
+ destinations += ", ";
+ destinations += (transfer.direction == "in" ? output.first.substr(0, 6) : output.first) + ":" + print_money(output.second);
}
}
+
+ auto formatter = boost::format("%8.8llu %6.6s %8.8s %25.25s %20.20s %s %s %14.14s %s %s - %s");
+
+ message_writer(color, false) << formatter
+ % transfer.block
+ % transfer.direction
+ % transfer.unlocked
+ % get_human_readable_timestamp(transfer.timestamp)
+ % print_money(transfer.amount)
+ % string_tools::pod_to_hex(transfer.hash)
+ % transfer.payment_id
+ % print_money(transfer.fee)
+ % destinations
+ % boost::algorithm::join(transfer.index | boost::adaptors::transformed([](uint32_t i) { return std::to_string(i); }), ", ")
+ % transfer.note;
}
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::export_transfers(const std::vector<std::string>& args_)
+{
+ std::vector<std::string> local_args = args_;
+
+ if(local_args.size() > 5) {
+ fail_msg_writer() << tr("usage: export_transfers [in|out|all|pending|failed|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<path>]");
+ return true;
+ }
+
+ LOCK_IDLE_SCOPE();
+
+ std::vector<transfer_view> all_transfers;
+
+ // might consumes arguments in local_args
+ if (!get_transfers(local_args, all_transfers))
+ return true;
+
+ // output filename
+ std::string filename = (boost::format("output%u.csv") % m_current_subaddress_account).str();
+ if (local_args.size() > 0 && local_args[0].substr(0, 7) == "output=")
+ {
+ filename = local_args[0].substr(7, -1);
+ local_args.erase(local_args.begin());
+ }
+
+ std::ofstream file(filename);
+
+ // header
+ file <<
+ boost::format("%8.8s,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%100.100s,%20.20s,%s,%s") %
+ tr("block") % tr("direction") % tr("unlocked") % tr("timestamp") % tr("amount") % tr("running balance") % tr("hash") % tr("payment ID") % tr("fee") % tr("destination") % tr("amount") % tr("index") % tr("note")
+ << std::endl;
+
+ uint64_t running_balance = 0;
+ auto formatter = boost::format("%8.8llu,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%100.100s,%20.20s,\"%s\",%s");
+
+ for (const auto& transfer : all_transfers)
+ {
+ // ignore unconfirmed transfers in running balance
+ if (transfer.confirmed)
+ {
+ if (transfer.direction == "in" || transfer.direction == "block")
+ running_balance += transfer.amount;
+ else
+ running_balance -= transfer.amount + transfer.fee;
+ }
+
+ file << formatter
+ % transfer.block
+ % transfer.direction
+ % transfer.unlocked
+ % get_human_readable_timestamp(transfer.timestamp)
+ % print_money(transfer.amount)
+ % print_money(running_balance)
+ % string_tools::pod_to_hex(transfer.hash)
+ % transfer.payment_id
+ % print_money(transfer.fee)
+ % (transfer.outputs.size() ? transfer.outputs[0].first : "-")
+ % (transfer.outputs.size() ? print_money(transfer.outputs[0].second) : "")
+ % boost::algorithm::join(transfer.index | boost::adaptors::transformed([](uint32_t i) { return std::to_string(i); }), ", ")
+ % transfer.note
+ << std::endl;
+
+ for (size_t i = 1; i < transfer.outputs.size(); ++i)
+ {
+ file << formatter
+ % ""
+ % ""
+ % ""
+ % ""
+ % ""
+ % ""
+ % ""
+ % ""
+ % ""
+ % transfer.outputs[i].first
+ % print_money(transfer.outputs[i].second)
+ % ""
+ % ""
+ << std::endl;
+ }
+ }
+ file.close();
+
+ success_msg_writer() << tr("CSV exported to ") << filename;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
{
if(args_.size() > 3)
@@ -6990,15 +7260,29 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
{
- message_writer() << tr("Warning: this will lose any information which can not be recovered from the blockchain.");
- message_writer() << tr("This includes destination addresses, tx secret keys, tx notes, etc");
- std::string confirm = input_line(tr("Rescan anyway ? (Y/Yes/N/No): "));
- if(!std::cin.eof())
+ bool hard = false;
+ if (!args_.empty())
{
- if (!command_line::is_yes(confirm))
+ if (args_[0] != "hard")
+ {
+ fail_msg_writer() << tr("usage: rescan_bc [hard]");
return true;
+ }
+ hard = true;
+ }
+
+ if (hard)
+ {
+ message_writer() << tr("Warning: this will lose any information which can not be recovered from the blockchain.");
+ message_writer() << tr("This includes destination addresses, tx secret keys, tx notes, etc");
+ std::string confirm = input_line(tr("Rescan anyway ? (Y/Yes/N/No): "));
+ if(!std::cin.eof())
+ {
+ if (!command_line::is_yes(confirm))
+ return true;
+ }
}
- return refresh_main(0, true);
+ return refresh_main(0, hard ? ResetHard : ResetSoft, true);
}
//----------------------------------------------------------------------------------------------------
void simple_wallet::wallet_idle_thread()
@@ -7046,7 +7330,7 @@ bool simple_wallet::run()
// check and display warning, but go on anyway
try_connect_to_daemon();
- refresh_main(0, false, true);
+ refresh_main(0, ResetNone, true);
m_auto_refresh_enabled = m_wallet->auto_refresh();
m_idle_thread = boost::thread([&]{wallet_idle_thread();});
@@ -7794,6 +8078,48 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &args)
+{
+ if (!m_wallet->key_on_device())
+ {
+ fail_msg_writer() << tr("command only supported by HW wallet");
+ return true;
+ }
+ if (!m_wallet->get_account().get_device().has_ki_cold_sync())
+ {
+ fail_msg_writer() << tr("hw wallet does not support cold KI sync");
+ return true;
+ }
+ if (!m_wallet->is_trusted_daemon())
+ {
+ fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
+ return true;
+ }
+
+ LOCK_IDLE_SCOPE();
+ try
+ {
+ message_writer(console_color_white, false) << tr("Please confirm the key image sync on the device");
+
+ uint64_t spent = 0, unspent = 0;
+ uint64_t height = m_wallet->cold_key_image_sync(spent, unspent);
+ if (height > 0)
+ {
+ success_msg_writer() << tr("Signed key images imported to height ") << height << ", "
+ << print_money(spent) << tr(" spent, ") << print_money(unspent) << tr(" unspent");
+ } else {
+ fail_msg_writer() << tr("Failed to import key images");
+ }
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to import key images: ") << e.what();
+ return true;
+ }
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::hw_reconnect(const std::vector<std::string> &args)
{
if (!m_wallet->key_on_device())