diff options
Diffstat (limited to 'src/simplewallet/simplewallet.cpp')
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 1957 |
1 files changed, 1684 insertions, 273 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d9fd0c13e..a8cc81869 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -65,6 +65,7 @@ #include "wallet/wallet_args.h" #include "version.h" #include <stdexcept> +#include "wallet/message_store.h" #ifdef WIN32 #include <boost/locale.hpp> @@ -102,12 +103,16 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) -#define SCOPED_WALLET_UNLOCK() \ +#define SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(code) \ LOCK_IDLE_SCOPE(); \ boost::optional<tools::password_container> pwd_container = boost::none; \ - if (m_wallet->ask_password() && !(pwd_container = get_and_verify_password())) { return true; } \ + if (m_wallet->ask_password() && !(pwd_container = get_and_verify_password())) { code; } \ tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container); +#define SCOPED_WALLET_UNLOCK() SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return true;) + +#define PRINT_USAGE(usage_help) fail_msg_writer() << boost::format(tr("usage: %s")) % usage_help; + enum TransferType { Transfer, TransferLocked, @@ -138,6 +143,93 @@ namespace const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; + const char* USAGE_START_MINING("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]"); + const char* USAGE_SET_DAEMON("set_daemon <host>[:<port>] [trusted|untrusted]"); + const char* USAGE_SHOW_BALANCE("balance [detail]"); + const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"); + const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]"); + const char* USAGE_PAYMENT_ID("payment_id"); + const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"); + const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id>]"); + const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id>]"); + const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]"); + const char* USAGE_SWEEP_BELOW("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"); + const char* USAGE_SWEEP_SINGLE("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"); + const char* USAGE_DONATE("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); + const char* USAGE_SIGN_TRANSFER("sign_transfer [export_raw]"); + const char* USAGE_SET_LOG("set_log <level>|{+,-,}<categories>"); + const char* USAGE_ACCOUNT("account\n" + " account new <label text with white spaces allowed>\n" + " account switch <index> \n" + " account label <index> <label text with white spaces allowed>\n" + " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" + " account untag <account_index_1> [<account_index_2> ...]\n" + " account tag_description <tag_name> <description>"); + const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"); + const char* USAGE_INTEGRATED_ADDRESS("integrated_address [<payment_id> | <address>]"); + const char* USAGE_ADDRESS_BOOK("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"); + const char* USAGE_SET_VARIABLE("set <option> [<value>]"); + const char* USAGE_GET_TX_KEY("get_tx_key <txid>"); + const char* USAGE_SET_TX_KEY("set_tx_key <txid> <tx_key>"); + const char* USAGE_CHECK_TX_KEY("check_tx_key <txid> <txkey> <address>"); + const char* USAGE_GET_TX_PROOF("get_tx_proof <txid> <address> [<message>]"); + const char* USAGE_CHECK_TX_PROOF("check_tx_proof <txid> <address> <signature_file> [<message>]"); + const char* USAGE_GET_SPEND_PROOF("get_spend_proof <txid> [<message>]"); + const char* USAGE_CHECK_SPEND_PROOF("check_spend_proof <txid> <signature_file> [<message>]"); + const char* USAGE_GET_RESERVE_PROOF("get_reserve_proof (all|<amount>) [<message>]"); + const char* USAGE_CHECK_RESERVE_PROOF("check_reserve_proof <address> <signature_file> [<message>]"); + const char* USAGE_SHOW_TRANSFERS("show_transfers [in|out|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"); + const char* USAGE_UNSPENT_OUTPUTS("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"); + const char* USAGE_RESCAN_BC("rescan_bc [hard]"); + const char* USAGE_SET_TX_NOTE("set_tx_note <txid> [free text note]"); + const char* USAGE_GET_TX_NOTE("get_tx_note <txid>"); + const char* USAGE_GET_DESCRIPTION("get_description"); + const char* USAGE_SET_DESCRIPTION("set_description [free text note]"); + const char* USAGE_SIGN("sign <filename>"); + const char* USAGE_VERIFY("verify <filename> <address> <signature>"); + const char* USAGE_EXPORT_KEY_IMAGES("export_key_images <filename>"); + const char* USAGE_IMPORT_KEY_IMAGES("import_key_images <filename>"); + const char* USAGE_HW_KEY_IMAGES_SYNC("hw_key_images_sync"); + const char* USAGE_HW_RECONNECT("hw_reconnect"); + const char* USAGE_EXPORT_OUTPUTS("export_outputs <filename>"); + const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); + const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); + const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); + const char* USAGE_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]"); + const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); + const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); + const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); + const char* USAGE_SIGN_MULTISIG("sign_multisig <filename>"); + const char* USAGE_SUBMIT_MULTISIG("submit_multisig <filename>"); + const char* USAGE_EXPORT_RAW_MULTISIG_TX("export_raw_multisig_tx <filename>"); + const char* USAGE_MMS("mms [<subcommand> [<subcommand_parameters>]]"); + const char* USAGE_MMS_INIT("mms init <required_signers>/<authorized_signers> <own_label> <own_transport_address>"); + const char* USAGE_MMS_INFO("mms info"); + const char* USAGE_MMS_SIGNER("mms signer [<number> <label> [<transport_address> [<monero_address>]]]"); + const char* USAGE_MMS_LIST("mms list"); + const char* USAGE_MMS_NEXT("mms next [sync]"); + const char* USAGE_MMS_SYNC("mms sync"); + const char* USAGE_MMS_TRANSFER("mms transfer <transfer_command_arguments>"); + const char* USAGE_MMS_DELETE("mms delete (<message_id> | all)"); + const char* USAGE_MMS_SEND("mms send [<message_id>]"); + const char* USAGE_MMS_RECEIVE("mms receive"); + const char* USAGE_MMS_EXPORT("mms export <message_id>"); + const char* USAGE_MMS_NOTE("mms note [<label> <text>]"); + const char* USAGE_MMS_SHOW("mms show <message_id>"); + const char* USAGE_MMS_SET("mms set <option_name> [<option_value>]"); + const char* USAGE_MMS_SEND_SIGNER_CONFIG("mms send_signer_config"); + const char* USAGE_MMS_START_AUTO_CONFIG("mms start_auto_config [<label> <label> ...]"); + const char* USAGE_MMS_STOP_AUTO_CONFIG("mms stop_auto_config"); + const char* USAGE_MMS_AUTO_CONFIG("mms auto_config <auto_config_token>"); + const char* USAGE_PRINT_RING("print_ring <key_image> | <txid>"); + const char* USAGE_SET_RING("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); + const char* USAGE_SAVE_KNOWN_RINGS("save_known_rings"); + const char* USAGE_MARK_OUTPUT_SPENT("mark_output_spent <amount>/<offset> | <filename> [add]"); + const char* USAGE_MARK_OUTPUT_UNSPENT("mark_output_unspent <amount>/<offset>"); + const char* USAGE_IS_OUTPUT_SPENT("is_output_spent <amount>/<offset>"); + const char* USAGE_VERSION("version"); + const char* USAGE_HELP("help [<command>]"); + std::string input_line(const std::string& prompt) { #ifdef HAVE_READLINE @@ -771,7 +863,7 @@ bool simple_wallet::payment_id(const std::vector<std::string> &args/* = std::vec crypto::hash payment_id; if (args.size() > 0) { - fail_msg_writer() << tr("usage: payment_id"); + PRINT_USAGE(USAGE_PAYMENT_ID); return true; } payment_id = crypto::rand<crypto::hash>(); @@ -836,65 +928,83 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std: bool simple_wallet::prepare_multisig(const std::vector<std::string> &args) { + prepare_multisig_main(args, false); + return true; +} + +bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (m_wallet->multisig()) { fail_msg_writer() << tr("This wallet is already multisig"); - return true; + return false; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); - return true; + return false; } if(m_wallet->get_num_transfer_details()) { fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); - return true; + return false; } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); std::string multisig_info = m_wallet->get_multisig_info(); success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); + + if (called_by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::key_set, multisig_info); + } + return true; } bool simple_wallet::make_multisig(const std::vector<std::string> &args) { + make_multisig_main(args, false); + return true; +} + +bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (m_wallet->multisig()) { fail_msg_writer() << tr("This wallet is already multisig"); - return true; + return false; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); - return true; + return false; } if(m_wallet->get_num_transfer_details()) { fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); - return true; + return false; } if (args.size() < 2) { - fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]"); - return true; + PRINT_USAGE(USAGE_MAKE_MULTISIG); + return false; } // parse threshold @@ -902,14 +1012,14 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args) if (!string_tools::get_xtype_from_string(threshold, args[0])) { fail_msg_writer() << tr("Invalid threshold"); - return true; + return false; } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); - return true; + return false; } LOCK_IDLE_SCOPE(); @@ -924,20 +1034,24 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args) success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info"); + if (called_by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::additional_key_set, multisig_extra_info); + } return true; } } catch (const std::exception &e) { fail_msg_writer() << tr("Error creating multisig: ") << e.what(); - return true; + return false; } uint32_t total; if (!m_wallet->multisig(NULL, &threshold, &total)) { fail_msg_writer() << tr("Error creating multisig: new wallet is not multisig"); - return true; + return false; } success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); @@ -976,7 +1090,7 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) if (args.size() < 2) { - fail_msg_writer() << tr("usage: finalize_multisig <multisiginfo1> [<multisiginfo2>...]"); + PRINT_USAGE(USAGE_FINALIZE_MULTISIG); return true; } @@ -997,35 +1111,41 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) return true; } -bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { +bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) +{ + exchange_multisig_keys_main(args, false); + return true; +} + +bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) { bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); - return true; + return false; } if (ready) { fail_msg_writer() << tr("This wallet is already finalized"); - return true; + return false; } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); - return true; + return false; } if (args.size() < 2) { - fail_msg_writer() << tr("usage: exchange_multisig_keys <multisiginfo1> [<multisiginfo2>...]"); - return true; + PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); + return false; } try @@ -1036,6 +1156,10 @@ bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) message_writer() << tr("Another step is needed"); message_writer() << multisig_extra_info; message_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info"); + if (called_by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::additional_key_set, multisig_extra_info); + } return true; } else { uint32_t threshold, total; @@ -1046,7 +1170,7 @@ bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) catch (const std::exception &e) { fail_msg_writer() << tr("Failed to perform multisig keys exchange: ") << e.what(); - return true; + return false; } return true; @@ -1054,50 +1178,63 @@ bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) bool simple_wallet::export_multisig(const std::vector<std::string> &args) { + export_multisig_main(args, false); + return true; +} + +bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_multisig_info <filename>"); - return true; + PRINT_USAGE(USAGE_EXPORT_MULTISIG_INFO); + return false; } const std::string filename = args[0]; - if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) + if (!called_by_mms && m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); try { cryptonote::blobdata ciphertext = m_wallet->export_multisig(); - bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("failed to save file ") << filename; - return true; + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::multisig_sync_data, ciphertext); + } + else + { + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return false; + } } } catch (const std::exception &e) { LOG_ERROR("Error exporting multisig info: " << e.what()); fail_msg_writer() << tr("Error exporting multisig info: ") << e.what(); - return true; + return false; } success_msg_writer() << tr("Multisig info exported to ") << filename; @@ -1106,44 +1243,57 @@ bool simple_wallet::export_multisig(const std::vector<std::string> &args) bool simple_wallet::import_multisig(const std::vector<std::string> &args) { + import_multisig_main(args, false); + return true; +} + +bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready, &threshold, &total)) { fail_msg_writer() << tr("This wallet is not multisig"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() < threshold - 1) { - fail_msg_writer() << tr("usage: import_multisig_info <filename1> [<filename2>...] - one for each other participant"); - return true; + PRINT_USAGE(USAGE_IMPORT_MULTISIG_INFO); + return false; } std::vector<cryptonote::blobdata> info; for (size_t n = 0; n < args.size(); ++n) { - const std::string filename = args[n]; - std::string data; - bool r = epee::file_io_utils::load_file_to_string(filename, data); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("failed to read file ") << filename; - return true; + info.push_back(args[n]); + } + else + { + const std::string &filename = args[n]; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return false; + } + info.push_back(std::move(data)); } - info.push_back(std::move(data)); } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); // all read and parsed, actually import try @@ -1158,7 +1308,7 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) catch (const std::exception &e) { fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); - return true; + return false; } if (m_wallet->is_trusted_daemon()) { @@ -1169,11 +1319,13 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) catch (const std::exception &e) { message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what(); + return false; } } else { message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\""); + return false; } return true; } @@ -1186,51 +1338,93 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector<std::string> &args) { + sign_multisig_main(args, false); + return true; +} + +bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if(!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This is not a multisig wallet"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() != 1) { - fail_msg_writer() << tr("usage: sign_multisig <filename>"); - return true; + PRINT_USAGE(USAGE_SIGN_MULTISIG); + return false; } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); std::string filename = args[0]; std::vector<crypto::hash> txids; uint32_t signers = 0; try { - bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("Failed to sign multisig transaction"); - return true; + tools::wallet2::multisig_tx_set exported_txs; + std::string ciphertext; + bool r = m_wallet->load_multisig_tx(args[0], exported_txs, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (r) + { + r = m_wallet->sign_multisig_tx(exported_txs, txids); + } + if (r) + { + ciphertext = m_wallet->save_multisig_tx(exported_txs); + if (ciphertext.empty()) + { + r = false; + } + } + if (r) + { + mms::message_type message_type = mms::message_type::fully_signed_tx; + if (txids.empty()) + { + message_type = mms::message_type::partially_signed_tx; + } + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), message_type, ciphertext); + filename = "MMS"; // for the messages below + } + else + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return false; + } + } + else + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return false; + } } } catch (const tools::error::multisig_export_needed& e) { fail_msg_writer() << tr("Multisig error: ") << e.what(); - return true; + return false; } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what(); - return true; + return false; } if (txids.empty()) @@ -1259,49 +1453,67 @@ bool simple_wallet::sign_multisig(const std::vector<std::string> &args) bool simple_wallet::submit_multisig(const std::vector<std::string> &args) { + submit_multisig_main(args, false); + return true; +} + +bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; uint32_t threshold; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready, &threshold)) { fail_msg_writer() << tr("This is not a multisig wallet"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() != 1) { - fail_msg_writer() << tr("usage: submit_multisig <filename>"); - return true; + PRINT_USAGE(USAGE_SUBMIT_MULTISIG); + return false; } if (!try_connect_to_daemon()) - return true; + return false; - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); std::string filename = args[0]; try { tools::wallet2::multisig_tx_set txs; - bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("Failed to load multisig transaction from file"); - return true; + bool r = m_wallet->load_multisig_tx(args[0], txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from MMS"); + return false; + } + } + else + { + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return false; + } } if (txs.m_signers.size() < threshold) { fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); - return true; + return false; } // actually commit the transactions @@ -1320,6 +1532,7 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args) { LOG_ERROR("unknown error"); fail_msg_writer() << tr("unknown error"); + return false; } return true; @@ -1346,7 +1559,7 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_raw_multisig <filename>"); + PRINT_USAGE(USAGE_EXPORT_RAW_MULTISIG_TX); return true; } @@ -1409,7 +1622,7 @@ bool simple_wallet::print_ring(const std::vector<std::string> &args) crypto::hash txid; if (args.size() != 1) { - fail_msg_writer() << tr("usage: print_ring <key_image|txid>"); + PRINT_USAGE(USAGE_PRINT_RING); return true; } @@ -1566,7 +1779,7 @@ bool simple_wallet::set_ring(const std::vector<std::string> &args) if (args.size() < 3) { - fail_msg_writer() << tr("usage: set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); + PRINT_USAGE(USAGE_SET_RING); return true; } @@ -1641,7 +1854,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args) uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; if (args.size() == 0) { - fail_msg_writer() << tr("usage: mark_output_spent <amount>/<offset> | <filename> [add]"); + PRINT_USAGE(USAGE_MARK_OUTPUT_SPENT); return true; } @@ -1730,7 +1943,7 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args) std::pair<uint64_t, uint64_t> output; if (args.size() != 1) { - fail_msg_writer() << tr("usage: mark_output_unspent <amount>/<offset>"); + PRINT_USAGE(USAGE_MARK_OUTPUT_UNSPENT); return true; } @@ -1757,7 +1970,7 @@ bool simple_wallet::blackballed(const std::vector<std::string> &args) std::pair<uint64_t, uint64_t> output; if (args.size() != 1) { - fail_msg_writer() << tr("usage: is_output_spent <amount>/<offset>"); + PRINT_USAGE(USAGE_IS_OUTPUT_SPENT); return true; } @@ -2308,6 +2521,12 @@ bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<st { success_msg_writer() << get_commands_str(); } + else if ((args.size() == 2) && (args.front() == "mms")) + { + // Little hack to be able to do "help mms <subcommand>" + std::vector<std::string> mms_args(1, args.front() + " " + args.back()); + success_msg_writer() << get_command_usage(mms_args); + } else { success_msg_writer() << get_command_usage(args); @@ -2326,14 +2545,14 @@ simple_wallet::simple_wallet() { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), - tr("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]"), + tr(USAGE_START_MINING), tr("Start mining in the daemon (bg_mining and ignore_battery are optional booleans).")); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in the daemon.")); m_cmd_binder.set_handler("set_daemon", boost::bind(&simple_wallet::set_daemon, this, _1), - tr("set_daemon <host>[:<port>] [trusted|untrusted]"), + tr(USAGE_SET_DAEMON), tr("Set another daemon to connect to.")); m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), @@ -2343,68 +2562,64 @@ simple_wallet::simple_wallet() tr("Synchronize the transactions and balance.")); m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), - tr("balance [detail]"), + tr(USAGE_SHOW_BALANCE), tr("Show the wallet's balance of the currently selected account.")); m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), - tr("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"), - tr("Show the incoming transfers, all or filtered by availability and address index.")); + tr(USAGE_INCOMING_TRANSFERS), + tr("Show the incoming transfers, all or filtered by availability and address index.\n\n" + "Output format:\n" + "Amount, Spent(\"T\"|\"F\"), \"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] ")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), - tr("payments <PID_1> [<PID_2> ... <PID_N>]"), + tr(USAGE_PAYMENTS), tr("Show the payments for the given payment IDs.")); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show the blockchain height.")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), - tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"), + tr(USAGE_TRANSFER), tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), - tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id>]"), + tr(USAGE_LOCKED_TRANSFER), tr("Transfer <amount> to <address> and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_sweep_all", boost::bind(&simple_wallet::locked_sweep_all, this, _1), - tr("locked_sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id>]"), + tr(USAGE_LOCKED_SWEEP_ALL), tr("Send all unlocked balance to an address and lock it for <lockblocks> (max. 1000000). If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. <priority> is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability.")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), - tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]"), + tr(USAGE_SWEEP_ALL), tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs.")); m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), - tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), + tr(USAGE_SWEEP_BELOW), tr("Send all unlocked outputs below the threshold to an address.")); m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), - tr("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"), + tr(USAGE_SWEEP_SINGLE), tr("Send a single output of the given key image to an address without change.")); m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), - tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"), + tr(USAGE_DONATE), tr("Donate <amount> to the development team (donate.getmonero.org).")); m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), - tr("sign_transfer [export_raw]"), + tr(USAGE_SIGN_TRANSFER), tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported.")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file.")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), - tr("set_log <level>|{+,-,}<categories>"), + tr(USAGE_SET_LOG), tr("Change the current log detail (level must be <0-4>).")); m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), - tr("account\n" - " account new <label text with white spaces allowed>\n" - " account switch <index> \n" - " account label <index> <label text with white spaces allowed>\n" - " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" - " account untag <account_index_1> [<account_index_2> ...]\n" - " account tag_description <tag_name> <description>"), + tr(USAGE_ACCOUNT), tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances.\n" "If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty).\n" "If the \"switch\" argument is specified, the wallet switches to the account specified by <index>.\n" @@ -2414,15 +2629,15 @@ simple_wallet::simple_wallet() "If the \"tag_description\" argument is specified, the tag <tag_name> is assigned an arbitrary text <description>.")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), - tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"), + tr(USAGE_ADDRESS), tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the wallet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), - tr("integrated_address [<payment_id> | <address>]"), + tr(USAGE_INTEGRATED_ADDRESS), tr("Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), - tr("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"), + tr(USAGE_ADDRESS_BOOK), tr("Print all entries in the address book, optionally adding/deleting an entry to/from it.")); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), @@ -2441,7 +2656,7 @@ simple_wallet::simple_wallet() tr("Display the Electrum-style mnemonic seed")); m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), - tr("set <option> [<value>]"), + tr(USAGE_SET_VARIABLE), tr("Available options:\n " "seed language\n " " Set the wallet's seed language.\n " @@ -2494,45 +2709,45 @@ simple_wallet::simple_wallet() tr("Rescan the blockchain for spent outputs.")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), - tr("get_tx_key <txid>"), + tr(USAGE_GET_TX_KEY), tr("Get the transaction key (r) for a given <txid>.")); m_cmd_binder.set_handler("set_tx_key", boost::bind(&simple_wallet::set_tx_key, this, _1), - tr("set_tx_key <txid> <tx_key>"), + tr(USAGE_SET_TX_KEY), tr("Set the transaction key (r) for a given <txid> in case the tx was made by some other device or 3rd party wallet.")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), - tr("check_tx_key <txid> <txkey> <address>"), + tr(USAGE_CHECK_TX_KEY), tr("Check the amount going to <address> in <txid>.")); m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), - tr("get_tx_proof <txid> <address> [<message>]"), + tr(USAGE_GET_TX_PROOF), tr("Generate a signature proving funds sent to <address> in <txid>, optionally with a challenge string <message>, using either the transaction secret key (when <address> is not your wallet's address) or the view secret key (otherwise), which does not disclose the secret key.")); m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), - tr("check_tx_proof <txid> <address> <signature_file> [<message>]"), + tr(USAGE_CHECK_TX_PROOF), tr("Check the proof for funds going to <address> in <txid> with the challenge string <message> if any.")); m_cmd_binder.set_handler("get_spend_proof", boost::bind(&simple_wallet::get_spend_proof, this, _1), - tr("get_spend_proof <txid> [<message>]"), + tr(USAGE_GET_SPEND_PROOF), tr("Generate a signature proving that you generated <txid> using the spend secret key, optionally with a challenge string <message>.")); m_cmd_binder.set_handler("check_spend_proof", boost::bind(&simple_wallet::check_spend_proof, this, _1), - tr("check_spend_proof <txid> <signature_file> [<message>]"), + tr(USAGE_CHECK_SPEND_PROOF), tr("Check a signature proving that the signer generated <txid>, optionally with a challenge string <message>.")); m_cmd_binder.set_handler("get_reserve_proof", boost::bind(&simple_wallet::get_reserve_proof, this, _1), - tr("get_reserve_proof (all|<amount>) [<message>]"), + tr(USAGE_GET_RESERVE_PROOF), tr("Generate a signature proving that you own at least this much, optionally with a challenge string <message>.\n" "If 'all' is specified, you prove the entire sum of all of your existing accounts' balances.\n" "Otherwise, you prove the reserve of the smallest possible amount above <amount> available in your current account.")); m_cmd_binder.set_handler("check_reserve_proof", boost::bind(&simple_wallet::check_reserve_proof, this, _1), - tr("check_reserve_proof <address> <signature_file> [<message>]"), + tr(USAGE_CHECK_RESERVE_PROOF), tr("Check a signature proving that the owner of <address> holds at least this much, optionally with a challenge string <message>.")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), - tr("show_transfers [in|out|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"), + tr(USAGE_SHOW_TRANSFERS), // Seemingly broken formatting to compensate for the backslash before the quotes. tr("Show the incoming/outgoing transfers within an optional height range.\n\n" "Output format:\n" @@ -2548,26 +2763,27 @@ simple_wallet::simple_wallet() 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(USAGE_UNSPENT_OUTPUTS), 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(USAGE_RESCAN_BC), 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), - tr("set_tx_note <txid> [free text note]"), + tr(USAGE_SET_TX_NOTE), tr("Set an arbitrary string note for a <txid>.")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), - tr("get_tx_note <txid>"), + tr(USAGE_GET_TX_NOTE), tr("Get a string note for a txid.")); m_cmd_binder.set_handler("set_description", boost::bind(&simple_wallet::set_description, this, _1), - tr("set_description [free text note]"), + tr(USAGE_SET_DESCRIPTION), tr("Set an arbitrary description for the wallet.")); m_cmd_binder.set_handler("get_description", boost::bind(&simple_wallet::get_description, this, _1), + tr(USAGE_GET_DESCRIPTION), tr("Get the description of the wallet.")); m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), @@ -2577,45 +2793,46 @@ simple_wallet::simple_wallet() tr("Show the wallet's information.")); m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), - tr("sign <file>"), + tr(USAGE_SIGN), tr("Sign the contents of a file.")); m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), - tr("verify <filename> <address> <signature>"), + tr(USAGE_VERIFY), tr("Verify a signature on the contents of a file.")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), - tr("export_key_images <file>"), - tr("Export a signed set of key images to a <file>.")); + tr(USAGE_EXPORT_KEY_IMAGES), + tr("Export a signed set of key images to a <filename>.")); m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), - tr("import_key_images <file>"), + tr(USAGE_IMPORT_KEY_IMAGES), 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(USAGE_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"), + tr(USAGE_HW_RECONNECT), tr("Attempts to reconnect HW wallet.")); m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), - tr("export_outputs <file>"), + tr(USAGE_EXPORT_OUTPUTS), tr("Export a set of outputs owned by this wallet.")); m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), - tr("import_outputs <file>"), + tr(USAGE_IMPORT_OUTPUTS), tr("Import a set of outputs owned by this wallet.")); m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), - tr("show_transfer <txid>"), + tr(USAGE_SHOW_TRANSFER), tr("Show information about a transfer to/from this address.")); m_cmd_binder.set_handler("password", boost::bind(&simple_wallet::change_password, this, _1), tr("Change the wallet's password.")); m_cmd_binder.set_handler("payment_id", boost::bind(&simple_wallet::payment_id, this, _1), + tr(USAGE_PAYMENT_ID), tr("Generate a new random full size payment id. These will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids.")); m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), @@ -2623,67 +2840,152 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1), tr("Export data needed to create a multisig wallet")); m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), - tr("make_multisig <threshold> <string1> [<string>...]"), + tr(USAGE_MAKE_MULTISIG), tr("Turn this wallet into a multisig wallet")); m_cmd_binder.set_handler("finalize_multisig", boost::bind(&simple_wallet::finalize_multisig, this, _1), - tr("finalize_multisig <string> [<string>...]"), + tr(USAGE_FINALIZE_MULTISIG), tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("exchange_multisig_keys", boost::bind(&simple_wallet::exchange_multisig_keys, this, _1), - tr("exchange_multisig_keys <string> [<string>...]"), + tr(USAGE_EXCHANGE_MULTISIG_KEYS), tr("Performs extra multisig keys exchange rounds. Needed for arbitrary M/N multisig wallets")); m_cmd_binder.set_handler("export_multisig_info", boost::bind(&simple_wallet::export_multisig, this, _1), - tr("export_multisig_info <filename>"), + tr(USAGE_EXPORT_MULTISIG_INFO), tr("Export multisig info for other participants")); m_cmd_binder.set_handler("import_multisig_info", boost::bind(&simple_wallet::import_multisig, this, _1), - tr("import_multisig_info <filename> [<filename>...]"), + tr(USAGE_IMPORT_MULTISIG_INFO), tr("Import multisig info from other participants")); m_cmd_binder.set_handler("sign_multisig", boost::bind(&simple_wallet::sign_multisig, this, _1), - tr("sign_multisig <filename>"), + tr(USAGE_SIGN_MULTISIG), tr("Sign a multisig transaction from a file")); m_cmd_binder.set_handler("submit_multisig", boost::bind(&simple_wallet::submit_multisig, this, _1), - tr("submit_multisig <filename>"), + tr(USAGE_SUBMIT_MULTISIG), tr("Submit a signed multisig transaction from a file")); m_cmd_binder.set_handler("export_raw_multisig_tx", boost::bind(&simple_wallet::export_raw_multisig, this, _1), - tr("export_raw_multisig_tx <filename>"), + tr(USAGE_EXPORT_RAW_MULTISIG_TX), tr("Export a signed multisig transaction to a file")); + m_cmd_binder.set_handler("mms", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS), + tr("Interface with the MMS (Multisig Messaging System)\n" + "<subcommand> is one of:\n" + " init, info, signer, list, next, sync, transfer, delete, send, receive, export, note, show, set, help\n" + " send_signer_config, start_auto_config, stop_auto_config, auto_config\n" + "Get help about a subcommand with: help mms <subcommand>, or mms help <subcommand>")); + m_cmd_binder.set_handler("mms init", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_INIT), + tr("Initialize and configure the MMS for M/N = number of required signers/number of authorized signers multisig")); + m_cmd_binder.set_handler("mms info", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_INFO), + tr("Display current MMS configuration")); + m_cmd_binder.set_handler("mms signer", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SIGNER), + tr("Set or modify authorized signer info (single-word label, transport address, Monero address), or list all signers")); + m_cmd_binder.set_handler("mms list", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_LIST), + tr("List all messages")); + m_cmd_binder.set_handler("mms next", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_NEXT), + tr("Evaluate the next possible multisig-related action(s) according to wallet state, and execute or offer for choice\n" + "By using 'sync' processing of waiting messages with multisig sync info can be forced regardless of wallet state")); + m_cmd_binder.set_handler("mms sync", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SYNC), + tr("Force generation of multisig sync info regardless of wallet state, to recover from special situations like \"stale data\" errors")); + m_cmd_binder.set_handler("mms transfer", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_TRANSFER), + tr("Initiate transfer with MMS support; arguments identical to normal 'transfer' command arguments, for info see there")); + m_cmd_binder.set_handler("mms delete", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_DELETE), + tr("Delete a single message by giving its id, or delete all messages by using 'all'")); + m_cmd_binder.set_handler("mms send", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SEND), + tr("Send a single message by giving its id, or send all waiting messages")); + m_cmd_binder.set_handler("mms receive", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_RECEIVE), + tr("Check right away for new messages to receive")); + m_cmd_binder.set_handler("mms export", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_EXPORT), + tr("Write the content of a message to a file \"mms_message_content\"")); + m_cmd_binder.set_handler("mms note", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_NOTE), + tr("Send a one-line message to an authorized signer, identified by its label, or show any waiting unread notes")); + m_cmd_binder.set_handler("mms show", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SHOW), + tr("Show detailed info about a single message")); + m_cmd_binder.set_handler("mms set", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SET), + tr("Available options:\n " + "auto-send <1|0>\n " + " Whether to automatically send newly generated messages right away.\n ")); + m_cmd_binder.set_handler("mms send_message_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SEND_SIGNER_CONFIG), + tr("Send completed signer config to all other authorized signers")); + m_cmd_binder.set_handler("mms start_auto_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_START_AUTO_CONFIG), + tr("Start auto-config at the auto-config manager's wallet by issuing auto-config tokens and optionally set others' labels")); + m_cmd_binder.set_handler("mms stop_auto_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_STOP_AUTO_CONFIG), + tr("Delete any auto-config tokens and abort a auto-config process")); + m_cmd_binder.set_handler("mms auto_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_AUTO_CONFIG), + tr("Start auto-config by using the token received from the auto-config manager")); m_cmd_binder.set_handler("print_ring", boost::bind(&simple_wallet::print_ring, this, _1), - tr("print_ring <key_image> | <txid>"), - tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)")); + tr(USAGE_PRINT_RING), + tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)\n\n" + "Output format:\n" + "Key Image, \"absolute\", list of rings")); m_cmd_binder.set_handler("set_ring", boost::bind(&simple_wallet::set_ring, this, _1), - tr("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"), + tr(USAGE_SET_RING), tr("Set the ring used for a given key image, so it can be reused in a fork")); m_cmd_binder.set_handler("save_known_rings", boost::bind(&simple_wallet::save_known_rings, this, _1), - tr("save_known_rings"), + tr(USAGE_SAVE_KNOWN_RINGS), tr("Save known rings to the shared rings database")); m_cmd_binder.set_handler("mark_output_spent", boost::bind(&simple_wallet::blackball, this, _1), - tr("mark_output_spent <amount>/<offset> | <filename> [add]"), + tr(USAGE_MARK_OUTPUT_SPENT), tr("Mark output(s) as spent so they never get selected as fake outputs in a ring")); m_cmd_binder.set_handler("mark_output_unspent", boost::bind(&simple_wallet::unblackball, this, _1), - tr("mark_output_unspent <amount>/<offset>"), + tr(USAGE_MARK_OUTPUT_UNSPENT), tr("Marks an output as unspent so it may get selected as a fake output in a ring")); m_cmd_binder.set_handler("is_output_spent", boost::bind(&simple_wallet::blackballed, this, _1), - tr("is_output_spent <amount>/<offset>"), + tr(USAGE_IS_OUTPUT_SPENT), tr("Checks whether an output is marked as spent")); m_cmd_binder.set_handler("version", boost::bind(&simple_wallet::version, this, _1), - tr("version"), + tr(USAGE_VERSION), tr("Returns version information")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), - tr("help [<command>]"), + tr(USAGE_HELP), tr("Show the help section or the documentation about a <command>.")); } //---------------------------------------------------------------------------------------------------- @@ -2797,7 +3099,7 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) { if(args.size() > 1) { - fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>"); + PRINT_USAGE(USAGE_SET_LOG); return true; } if(!args.empty()) @@ -2807,7 +3109,7 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) { if(4 < level) { - fail_msg_writer() << tr("wrong number range, use: set_log <log_level_number_0-4> | <categories>"); + fail_msg_writer() << boost::format(tr("wrong number range, use: %s")) % USAGE_SET_LOG; return true; } mlog_set_log_level(level); @@ -3786,6 +4088,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr { auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); + m_wallet->callback(this); if (!m_wallet) { return {}; @@ -3803,9 +4106,11 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr m_wallet->set_refresh_from_block_height(m_restore_height); auto device_desc = tools::wallet2::device_name_option(vm); + auto device_derivation_path = tools::wallet2::device_derivation_path_option(vm); try { bool create_address_file = command_line::get_arg(vm, arg_create_address_file); + m_wallet->device_derivation_path(device_derivation_path); m_wallet->restore(m_wallet_file, std::move(rc.second).password(), device_desc.empty() ? "Ledger" : device_desc, create_address_file); message_writer(console_color_white, true) << tr("Generated new wallet on hw device: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); @@ -3893,7 +4198,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) epee::wipeable_string password; try { - auto rc = tools::wallet2::make_from_file(vm, false, m_wallet_file, password_prompter); + auto rc = tools::wallet2::make_from_file(vm, false, "", password_prompter); m_wallet = std::move(rc.first); password = std::move(std::move(rc.second).password()); if (!m_wallet) @@ -3901,6 +4206,8 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) return false; } + m_wallet->callback(this); + m_wallet->load(m_wallet_file, password); std::string prefix; bool ready; uint32_t threshold, total; @@ -4098,7 +4405,7 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) if (!ok) { - fail_msg_writer() << tr("invalid arguments. Please use start_mining [<number_of_threads>] [do_bg_mining] [ignore_battery]"); + PRINT_USAGE(USAGE_START_MINING); return true; } @@ -4140,7 +4447,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) if (args.size() < 1) { - fail_msg_writer() << tr("missing daemon URL argument"); + PRINT_USAGE(USAGE_SET_DAEMON); return true; } @@ -4304,6 +4611,61 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char return pwd_container->password(); } //---------------------------------------------------------------------------------------------------- +void simple_wallet::on_button_request() +{ + message_writer(console_color_white, false) << tr("Device requires attention"); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_pin_request(epee::wipeable_string & pin) +{ +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + std::string msg = tr("Enter device PIN"); + auto pwd_container = tools::password_container::prompt(false, msg.c_str()); + THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device PIN")); + pin = pwd_container->password(); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) +{ + if (on_device){ + message_writer(console_color_white, true) << tr("Please enter the device passphrase on the device"); + return; + } + +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + std::string msg = tr("Enter device passphrase"); + auto pwd_container = tools::password_container::prompt(false, msg.c_str()); + THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device passphrase")); + passphrase = pwd_container->password(); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money) +{ + // Key image sync after the first refresh + if (!m_wallet->get_account().get_device().has_tx_cold_sign()) { + return; + } + + if (!received_money || m_wallet->get_device_last_key_image_sync() != 0) { + return; + } + + // Finished first refresh for HW device and money received -> KI sync + message_writer() << "\n" << tr("The first refresh has finished for the HW-based wallet with received money. hw_key_images_sync is needed. "); + + std::string accepted = input_line(tr("Do you want to do it now? (Y/Yes/N/No): ")); + if (std::cin.eof() || !command_line::is_yes(accepted)) { + message_writer(console_color_red, false) << tr("hw_key_images_sync skipped. Run command manually before a transfer."); + return; + } + + key_images_sync_intern(); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bool is_init) { if (!try_connect_to_daemon(is_init)) @@ -4321,13 +4683,14 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo message_writer() << tr("Starting refresh..."); uint64_t fetched_blocks = 0; + bool received_money = false; bool ok = false; std::ostringstream ss; try { m_in_manual_refresh.store(true, std::memory_order_relaxed); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); - m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks); + m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money); ok = true; // Clear line "Height xxx of xxx" std::cout << "\r \r"; @@ -4335,6 +4698,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo if (is_init) print_accounts(); show_balance_unlocked(); + on_refresh_finished(start_height, fetched_blocks, is_init, received_money); } catch (const tools::error::daemon_busy&) { @@ -4428,7 +4792,7 @@ bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::v { if (args.size() > 1 || (args.size() == 1 && args[0] != "detail")) { - fail_msg_writer() << tr("usage: balance [detail]"); + PRINT_USAGE(USAGE_SHOW_BALANCE); return true; } LOCK_IDLE_SCOPE(); @@ -4440,7 +4804,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (args.size() > 3) { - fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N>]"); + PRINT_USAGE(USAGE_INCOMING_TRANSFERS); return true; } auto local_args = args; @@ -4482,7 +4846,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args if (local_args.size() > 0) { - fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N>]"); + PRINT_USAGE(USAGE_INCOMING_TRANSFERS); return true; } @@ -4547,7 +4911,7 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args) { if(args.empty()) { - fail_msg_writer() << tr("expected at least one payment ID"); + PRINT_USAGE(USAGE_PAYMENTS); return true; } @@ -4777,11 +5141,11 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) +bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_, bool called_by_mms) { // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" if (!try_connect_to_daemon()) - return true; + return false; std::vector<std::string> local_args = args_; @@ -4789,7 +5153,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri 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()); } @@ -4811,7 +5175,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri else if (ring_size == 0) { fail_msg_writer() << tr("Ring size must not be 0"); - return true; + return false; } else { @@ -4823,19 +5187,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (adjusted_fake_outs_count > fake_outs_count) { fail_msg_writer() << (boost::format(tr("ring size %u is too small, minimum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); - return true; + return false; } if (adjusted_fake_outs_count < fake_outs_count) { fail_msg_writer() << (boost::format(tr("ring size %u is too large, maximum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); - return true; + return false; } const size_t min_args = (transfer_type == TransferLocked) ? 2 : 1; if(local_args.size() < min_args) { fail_msg_writer() << tr("wrong number of arguments"); - return true; + return false; } std::vector<uint8_t> extra; @@ -4870,7 +5234,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if(!r) { fail_msg_writer() << tr("payment id failed to encode"); - return true; + return false; } } @@ -4884,12 +5248,12 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri catch (const std::exception &e) { fail_msg_writer() << tr("bad locked_blocks parameter:") << " " << local_args.back(); - return true; + return false; } if (locked_blocks > 1000000) { fail_msg_writer() << tr("Locked blocks too high, max 1000000 (˜4 yrs)"); - return true; + return false; } local_args.pop_back(); } @@ -4917,7 +5281,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!tools::wallet2::parse_short_payment_id(payment_id_uri, info.payment_id)) { fail_msg_writer() << tr("failed to parse short payment ID from URI"); - return true; + return false; } info.has_payment_id = true; } @@ -4932,7 +5296,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { fail_msg_writer() << tr("amount is wrong: ") << local_args[i] << ' ' << local_args[i + 1] << ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max()); - return true; + return false; } i += 2; } @@ -4942,13 +5306,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri fail_msg_writer() << tr("Invalid last argument: ") << local_args.back() << ": " << error; else fail_msg_writer() << tr("Invalid last argument: ") << local_args.back(); - return true; + return false; } if (!r) { fail_msg_writer() << tr("failed to parse address"); - return true; + return false; } de.addr = info.address; de.is_subaddress = info.is_subaddress; @@ -4959,7 +5323,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (payment_id_seen) { fail_msg_writer() << tr("a single transaction cannot use more than one payment id"); - return true; + return false; } crypto::hash payment_id; @@ -4976,13 +5340,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri else { fail_msg_writer() << tr("failed to parse payment id, though it was detected"); - return true; + return false; } bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if(!r) { fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); - return true; + return false; } payment_id_seen = true; } @@ -4995,16 +5359,16 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); if (std::cin.eof()) - return true; + return false; if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - return true; + return false; } } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); try { @@ -5019,7 +5383,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!err.empty()) { fail_msg_writer() << tr("failed to get blockchain height: ") << err; - return true; + return false; } unlock_block = bc_height + locked_blocks; ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); @@ -5035,7 +5399,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (ptx_vector.empty()) { fail_msg_writer() << tr("No outputs found, or daemon is not ready"); - return true; + return false; } // if we need to check for backlog, check the worst case tx @@ -5075,12 +5439,12 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { std::string accepted = input_line(prompt_str); if (std::cin.eof()) - return true; + return false; if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - return true; + return false; } } } @@ -5140,7 +5504,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (m_wallet->print_ring_members()) { if (!print_ring_members(ptx_vector, prompt)) - return true; + return false; } bool default_ring_size = true; for (const auto &ptx: ptx_vector) @@ -5163,22 +5527,32 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri std::string accepted = input_line(prompt.str()); if (std::cin.eof()) - return true; + return false; if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - return true; + return false; } } // actually commit the transactions - if (m_wallet->multisig()) + if (m_wallet->multisig() && called_by_mms) + { + std::string ciphertext = m_wallet->save_multisig_tx(ptx_vector); + if (!ciphertext.empty()) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::partially_signed_tx, ciphertext); + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to MMS"); + } + } + else if (m_wallet->multisig()) { bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { fail_msg_writer() << tr("Failed to write transaction(s) to file"); + return false; } else { @@ -5192,7 +5566,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri 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; + return false; } commit_or_save(signed_tx.ptx, m_do_not_relay); @@ -5200,11 +5574,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri catch (const std::exception& e) { handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + return false; } catch (...) { LOG_ERROR("Unknown error"); fail_msg_writer() << tr("unknown error"); + return false; } } else if (m_wallet->watch_only()) @@ -5213,6 +5589,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!r) { fail_msg_writer() << tr("Failed to write transaction(s) to file"); + return false; } else { @@ -5227,11 +5604,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri catch (const std::exception &e) { handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + return false; } catch (...) { LOG_ERROR("unknown error"); fail_msg_writer() << tr("unknown error"); + return false; } return true; @@ -5239,12 +5618,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { - return transfer_main(Transfer, args_); + transfer_main(Transfer, args_, false); + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) { - return transfer_main(TransferLocked, args_); + transfer_main(TransferLocked, args_, false); + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) @@ -5363,7 +5744,14 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st { auto print_usage = [below]() { - fail_msg_writer() << boost::format(tr("usage: %s [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]")) % (below ? "sweep_below" : "sweep_all"); + if (below) + { + PRINT_USAGE(USAGE_SWEEP_BELOW); + } + else + { + PRINT_USAGE(USAGE_SWEEP_ALL); + } }; if (args_.size() == 0) { @@ -5781,7 +6169,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) if (local_args.size() != 2) { - fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"); + PRINT_USAGE(USAGE_SWEEP_SINGLE); return true; } @@ -5942,16 +6330,10 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::donate(const std::vector<std::string> &args_) { - if(m_wallet->nettype() != cryptonote::MAINNET) - { - fail_msg_writer() << tr("donations are not enabled on the testnet or on the stagenet"); - return true; - } - std::vector<std::string> local_args = args_; if(local_args.empty() || local_args.size() > 5) { - fail_msg_writer() << tr("usage: donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); + PRINT_USAGE(USAGE_DONATE); return true; } std::string amount_str; @@ -5969,11 +6351,30 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) amount_str = local_args.back(); local_args.pop_back(); // push back address, amount, payment id - local_args.push_back(MONERO_DONATION_ADDR); + std::string address_str; + if (m_wallet->nettype() != cryptonote::MAINNET) + { + // if not mainnet, convert donation address string to the relevant network type + address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, MONERO_DONATION_ADDR)) + { + fail_msg_writer() << tr("Failed to parse donation address: ") << MONERO_DONATION_ADDR; + return true; + } + address_str = cryptonote::get_account_address_as_str(m_wallet->nettype(), info.is_subaddress, info.address); + } + else + { + address_str = MONERO_DONATION_ADDR; + } + local_args.push_back(address_str); local_args.push_back(amount_str); if (!payment_id_str.empty()) local_args.push_back(payment_id_str); - message_writer() << (boost::format(tr("Donating %s %s to The Monero Project (donate.getmonero.org or %s).")) % amount_str % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % MONERO_DONATION_ADDR).str(); + if (m_wallet->nettype() == cryptonote::MAINNET) + message_writer() << (boost::format(tr("Donating %s %s to The Monero Project (donate.getmonero.org or %s).")) % amount_str % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % MONERO_DONATION_ADDR).str(); + else + message_writer() << (boost::format(tr("Donating %s %s to %s.")) % amount_str % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % address_str).str(); transfer(local_args); return true; } @@ -6145,7 +6546,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) } if (args_.size() > 1 || (args_.size() == 1 && args_[0] != "export_raw")) { - fail_msg_writer() << tr("usage: sign_transfer [export_raw]"); + PRINT_USAGE(USAGE_SIGN_TRANSFER); return true; } @@ -6235,7 +6636,7 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) return true; } if(local_args.size() != 1) { - fail_msg_writer() << tr("usage: get_tx_key <txid>"); + PRINT_USAGE(USAGE_GET_TX_KEY); return true; } @@ -6271,7 +6672,7 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.size() != 2) { - fail_msg_writer() << tr("usage: set_tx_key <txid> <tx_key>"); + PRINT_USAGE(USAGE_SET_TX_KEY); return true; } @@ -6333,7 +6734,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) } if (args.size() != 2 && args.size() != 3) { - fail_msg_writer() << tr("usage: get_tx_proof <txid> <address> [<message>]"); + PRINT_USAGE(USAGE_GET_TX_PROOF); return true; } @@ -6374,7 +6775,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.size() != 3) { - fail_msg_writer() << tr("usage: check_tx_key <txid> <txkey> <address>"); + PRINT_USAGE(USAGE_CHECK_TX_KEY); return true; } @@ -6460,7 +6861,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) { if(args.size() != 3 && args.size() != 4) { - fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature_file> [<message>]"); + PRINT_USAGE(USAGE_CHECK_TX_PROOF); return true; } @@ -6543,7 +6944,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) return true; } if(args.size() != 1 && args.size() != 2) { - fail_msg_writer() << tr("usage: get_spend_proof <txid> [<message>]"); + PRINT_USAGE(USAGE_GET_SPEND_PROOF); return true; } @@ -6584,7 +6985,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) { if(args.size() != 2 && args.size() != 3) { - fail_msg_writer() << tr("usage: check_spend_proof <txid> <signature_file> [<message>]"); + PRINT_USAGE(USAGE_CHECK_SPEND_PROOF); return true; } @@ -6627,7 +7028,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) return true; } if(args.size() != 1 && args.size() != 2) { - fail_msg_writer() << tr("usage: get_reserve_proof (all|<amount>) [<message>]"); + PRINT_USAGE(USAGE_GET_RESERVE_PROOF); return true; } @@ -6673,7 +7074,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args) { if(args.size() != 2 && args.size() != 3) { - fail_msg_writer() << tr("usage: check_reserve_proof <address> <signature_file> [<message>]"); + PRINT_USAGE(USAGE_CHECK_RESERVE_PROOF); return true; } @@ -6720,24 +7121,6 @@ bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- -static std::string get_human_readable_timestamp(uint64_t ts) -{ - char buffer[64]; - if (ts < 1234567890) - return "<unknown>"; - time_t tt = ts; - struct tm tm; -#ifdef WIN32 - gmtime_s(&tm, &tt); -#else - gmtime_r(&tt, &tm); -#endif - uint64_t now = time(NULL); - uint64_t diff = ts > now ? ts - now : now - ts; - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); - return std::string(buffer); -} -//---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timespan(std::chrono::seconds seconds) { uint64_t ts = seconds.count(); @@ -6844,7 +7227,7 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec 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"); - const bool unlocked = m_wallet->is_tx_spendtime_unlocked(pd.m_unlock_time, pd.m_block_height); + const bool unlocked = m_wallet->is_transfer_unlocked(pd.m_unlock_time, pd.m_block_height); transfers.push_back({ pd.m_block_height, pd.m_timestamp, @@ -7022,7 +7405,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) % transfer.block % transfer.direction % transfer.unlocked - % get_human_readable_timestamp(transfer.timestamp) + % tools::get_human_readable_timestamp(transfer.timestamp) % print_money(transfer.amount) % string_tools::pod_to_hex(transfer.hash) % transfer.payment_id @@ -7086,7 +7469,7 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) % transfer.block % transfer.direction % transfer.unlocked - % get_human_readable_timestamp(transfer.timestamp) + % tools::get_human_readable_timestamp(transfer.timestamp) % print_money(transfer.amount) % print_money(running_balance) % string_tools::pod_to_hex(transfer.hash) @@ -7128,7 +7511,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) { if(args_.size() > 3) { - fail_msg_writer() << tr("usage: unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"); + PRINT_USAGE(USAGE_UNSPENT_OUTPUTS); return true; } auto local_args = args_; @@ -7269,7 +7652,7 @@ bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) { if (args_[0] != "hard") { - fail_msg_writer() << tr("usage: rescan_bc [hard]"); + PRINT_USAGE(USAGE_RESCAN_BC); return true; } hard = true; @@ -7289,6 +7672,22 @@ bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) return refresh_main(0, hard ? ResetHard : ResetSoft, true); } //---------------------------------------------------------------------------------------------------- +void simple_wallet::check_for_messages() +{ + try + { + std::vector<mms::message> new_messages; + bool new_message = get_message_store().check_for_messages(get_multisig_wallet_state(), new_messages); + if (new_message) + { + message_writer(console_color_magenta, true) << tr("MMS received new message"); + list_mms_messages(new_messages); + m_cmd_binder.print_prompt(); + } + } + catch(...) {} +} +//---------------------------------------------------------------------------------------------------- void simple_wallet::wallet_idle_thread() { while (true) @@ -7311,6 +7710,14 @@ void simple_wallet::wallet_idle_thread() m_auto_refresh_refreshing = false; } + // Check for new MMS messages; + // For simplicity auto message check is ALSO controlled by "m_auto_refresh_enabled" and has no + // separate thread either; thread syncing is tricky enough with only this one idle thread here + if (m_auto_refresh_enabled && get_message_store().get_active()) + { + check_for_messages(); + } + if (!m_idle_run.load(std::memory_order_relaxed)) break; m_idle_cond.wait_for(lock, boost::chrono::seconds(90)); @@ -7490,14 +7897,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector } else { - fail_msg_writer() << tr("usage:\n" - " account\n" - " account new <label text with white spaces allowed>\n" - " account switch <index>\n" - " account label <index> <label text with white spaces allowed>\n" - " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" - " account untag <account_index_1> [<account_index_2> ...]\n" - " account tag_description <tag_name> <description>"); + PRINT_USAGE(USAGE_ACCOUNT); } return true; } @@ -7652,7 +8052,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: } else { - fail_msg_writer() << tr("usage: address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ]"); + PRINT_USAGE(USAGE_ADDRESS); } return true; @@ -7663,7 +8063,7 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg crypto::hash8 payment_id; if (args.size() > 1) { - fail_msg_writer() << tr("usage: integrated_address [payment ID]"); + PRINT_USAGE(USAGE_INTEGRATED_ADDRESS); return true; } if (args.size() == 0) @@ -7715,7 +8115,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v } else if (args.size() == 1 || (args[0] != "add" && args[0] != "delete")) { - fail_msg_writer() << tr("usage: address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)]"); + PRINT_USAGE(USAGE_ADDRESS_BOOK); return true; } else if (args[0] == "add") @@ -7790,7 +8190,7 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args) { if (args.size() == 0) { - fail_msg_writer() << tr("usage: set_tx_note [txid] free text note"); + PRINT_USAGE(USAGE_SET_TX_NOTE); return true; } @@ -7818,7 +8218,7 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) { if (args.size() != 1) { - fail_msg_writer() << tr("usage: get_tx_note [txid]"); + PRINT_USAGE(USAGE_GET_TX_NOTE); return true; } @@ -7859,7 +8259,7 @@ bool simple_wallet::get_description(const std::vector<std::string> &args) { if (args.size() != 0) { - fail_msg_writer() << tr("usage: get_description"); + PRINT_USAGE(USAGE_GET_DESCRIPTION); return true; } @@ -7932,7 +8332,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: sign <filename>"); + PRINT_USAGE(USAGE_SIGN); return true; } if (m_wallet->watch_only()) @@ -7966,7 +8366,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args) { if (args.size() != 3) { - fail_msg_writer() << tr("usage: verify <filename> <address> <signature>"); + PRINT_USAGE(USAGE_VERIFY); return true; } std::string filename = args[0]; @@ -8009,7 +8409,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_key_images <filename>"); + PRINT_USAGE(USAGE_EXPORT_KEY_IMAGES); return true; } if (m_wallet->watch_only()) @@ -8058,7 +8458,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) if (args.size() != 1) { - fail_msg_writer() << tr("usage: import_key_images <filename>"); + PRINT_USAGE(USAGE_IMPORT_KEY_IMAGES); return true; } std::string filename = args[0]; @@ -8097,13 +8497,13 @@ bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &args) 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(); + key_images_sync_intern(); + return true; +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::key_images_sync_intern(){ try { message_writer(console_color_white, false) << tr("Please confirm the key image sync on the device"); @@ -8112,19 +8512,23 @@ bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &args) 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 { + success_msg_writer() << tr("Key images synchronized to height ") << height; + if (!m_wallet->is_trusted_daemon()) + { + message_writer() << tr("Running untrusted daemon, cannot determine which transaction output is spent. Use a trusted daemon with --trusted-daemon and run rescan_spent"); + } else + { + success_msg_writer() << 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) @@ -8161,7 +8565,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_outputs <filename>"); + PRINT_USAGE(USAGE_EXPORT_OUTPUTS); return true; } @@ -8201,7 +8605,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: import_outputs <filename>"); + PRINT_USAGE(USAGE_IMPORT_OUTPUTS); return true; } std::string filename = args[0]; @@ -8233,7 +8637,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) { if (args.size() != 1) { - fail_msg_writer() << tr("usage: show_transfer <txid>"); + PRINT_USAGE(USAGE_SHOW_TRANSFER); return true; } @@ -8258,7 +8662,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << "Incoming transaction found"; success_msg_writer() << "txid: " << txid; success_msg_writer() << "Height: " << pd.m_block_height; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount); success_msg_writer() << "Payment ID: " << payment_id; if (pd.m_unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) @@ -8308,7 +8712,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << "Outgoing transaction found"; success_msg_writer() << "txid: " << txid; success_msg_writer() << "Height: " << pd.m_block_height; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount_in - change - fee); success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Change: " << print_money(change); @@ -8333,7 +8737,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) payment_id = payment_id.substr(0,16); success_msg_writer() << "Unconfirmed incoming transaction found in the txpool"; success_msg_writer() << "txid: " << txid; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount); success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor; @@ -8364,7 +8768,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << (is_failed ? "Failed" : "Pending") << " outgoing transaction found"; success_msg_writer() << "txid: " << txid; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(amount - pd.m_change - fee); success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Change: " << print_money(pd.m_change); @@ -8465,7 +8869,7 @@ int main(int argc, char* argv[]) bool should_terminate = false; std::tie(vm, should_terminate) = wallet_args::main( argc, argv, - "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", + "monero-wallet-cli [--wallet-file=<filename>|--generate-new-wallet=<filename>] [<COMMAND>]", sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly.\nWARNING: Do not reuse your Monero keys on another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy."), desc_params, positional_options, @@ -8524,3 +8928,1010 @@ int main(int argc, char* argv[]) return 0; CATCH_ENTRY_L0("main", 1); } + +// MMS --------------------------------------------------------------------------------------------------- + +// Access to the message store, or more exactly to the list of the messages that can be changed +// by the idle thread, is guarded by the same mutex-based mechanism as access to the wallet +// as a whole and thus e.g. uses the "LOCK_IDLE_SCOPE" macro. This is a little over-cautious, but +// simple and safe. Care has to be taken however where MMS methods call other simplewallet methods +// that use "LOCK_IDLE_SCOPE" as this cannot be nested! + +// Methods for commands like "export_multisig_info" usually read data from file(s) or write data +// to files. The MMS calls now those methods as well, to produce data for messages and to process data +// from messages. As it would be quite inconvenient for the MMS to write data for such methods to files +// first or get data out of result files after the call, those methods detect a call from the MMS and +// expect data as arguments instead of files and give back data by calling 'process_wallet_created_data'. + +bool simple_wallet::user_confirms(const std::string &question) +{ + std::string answer = input_line(question + tr(" (Y/Yes/N/No): ")); + return !std::cin.eof() && command_line::is_yes(answer); +} + +bool simple_wallet::get_number_from_arg(const std::string &arg, uint32_t &number, const uint32_t lower_bound, const uint32_t upper_bound) +{ + bool valid = false; + try + { + number = boost::lexical_cast<uint32_t>(arg); + valid = (number >= lower_bound) && (number <= upper_bound); + } + catch(const boost::bad_lexical_cast &) + { + } + return valid; +} + +bool simple_wallet::choose_mms_processing(const std::vector<mms::processing_data> &data_list, uint32_t &choice) +{ + size_t choices = data_list.size(); + if (choices == 1) + { + choice = 0; + return true; + } + mms::message_store& ms = m_wallet->get_message_store(); + message_writer() << tr("Choose processing:"); + std::string text; + for (size_t i = 0; i < choices; ++i) + { + const mms::processing_data &data = data_list[i]; + text = std::to_string(i+1) + ": "; + switch (data.processing) + { + case mms::message_processing::sign_tx: + text += tr("Sign tx"); + break; + case mms::message_processing::send_tx: + { + mms::message m; + ms.get_message_by_id(data.message_ids[0], m); + if (m.type == mms::message_type::fully_signed_tx) + { + text += tr("Send the tx for submission to "); + } + else + { + text += tr("Send the tx for signing to "); + } + mms::authorized_signer signer = ms.get_signer(data.receiving_signer_index); + text += ms.signer_to_string(signer, 50); + break; + } + case mms::message_processing::submit_tx: + text += tr("Submit tx"); + break; + default: + text += tr("unknown"); + break; + } + message_writer() << text; + } + + std::string line = input_line(tr("Choice: ")); + if (std::cin.eof() || line.empty()) + { + return false; + } + bool choice_ok = get_number_from_arg(line, choice, 1, choices); + if (choice_ok) + { + choice--; + } + else + { + fail_msg_writer() << tr("Wrong choice"); + } + return choice_ok; +} + +void simple_wallet::list_mms_messages(const std::vector<mms::message> &messages) +{ + message_writer() << boost::format("%4s %-4s %-30s %-21s %7s %3s %-15s %-40s") % tr("Id") % tr("I/O") % tr("Authorized Signer") + % tr("Message Type") % tr("Height") % tr("R") % tr("Message State") % tr("Since"); + mms::message_store& ms = m_wallet->get_message_store(); + uint64_t now = (uint64_t)time(NULL); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + const mms::authorized_signer &signer = ms.get_signer(m.signer_index); + bool highlight = (m.state == mms::message_state::ready_to_send) || (m.state == mms::message_state::waiting); + message_writer(m.direction == mms::message_direction::out ? console_color_green : console_color_magenta, highlight) << + boost::format("%4s %-4s %-30s %-21s %7s %3s %-15s %-40s") % + m.id % + ms.message_direction_to_string(m.direction) % + ms.signer_to_string(signer, 30) % + ms.message_type_to_string(m.type) % + m.wallet_height % + m.round % + ms.message_state_to_string(m.state) % + (tools::get_human_readable_timestamp(m.modified) + ", " + get_human_readable_timespan(std::chrono::seconds(now - m.modified)) + tr(" ago")); + } +} + +void simple_wallet::list_signers(const std::vector<mms::authorized_signer> &signers) +{ + message_writer() << boost::format("%2s %-20s %-s") % tr("#") % tr("Label") % tr("Transport Address"); + message_writer() << boost::format("%2s %-20s %-s") % "" % tr("Auto-Config Token") % tr("Monero Address"); + for (size_t i = 0; i < signers.size(); ++i) + { + const mms::authorized_signer &signer = signers[i]; + std::string label = signer.label.empty() ? tr("<not set>") : signer.label; + std::string monero_address; + if (signer.monero_address_known) + { + monero_address = get_account_address_as_str(m_wallet->nettype(), false, signer.monero_address); + } + else + { + monero_address = tr("<not set>"); + } + std::string transport_address = signer.transport_address.empty() ? tr("<not set>") : signer.transport_address; + message_writer() << boost::format("%2s %-20s %-s") % (i + 1) % label % transport_address; + message_writer() << boost::format("%2s %-20s %-s") % "" % signer.auto_config_token % monero_address; + message_writer() << ""; + } +} + +void simple_wallet::add_signer_config_messages() +{ + mms::message_store& ms = m_wallet->get_message_store(); + std::string signer_config; + ms.get_signer_config(signer_config); + + const std::vector<mms::authorized_signer> signers = ms.get_all_signers(); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + uint32_t num_authorized_signers = ms.get_num_authorized_signers(); + for (uint32_t i = 1 /* without me */; i < num_authorized_signers; ++i) + { + ms.add_message(state, i, mms::message_type::signer_config, mms::message_direction::out, signer_config); + } +} + +void simple_wallet::show_message(const mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + const mms::authorized_signer &signer = ms.get_signer(m.signer_index); + bool display_content; + std::string sanitized_text; + switch (m.type) + { + case mms::message_type::key_set: + case mms::message_type::additional_key_set: + case mms::message_type::note: + display_content = true; + ms.get_sanitized_message_text(m, sanitized_text); + break; + default: + display_content = false; + } + uint64_t now = (uint64_t)time(NULL); + message_writer() << ""; + message_writer() << tr("Message ") << m.id; + message_writer() << tr("In/out: ") << ms.message_direction_to_string(m.direction); + message_writer() << tr("Type: ") << ms.message_type_to_string(m.type); + message_writer() << tr("State: ") << boost::format(tr("%s since %s, %s ago")) % + ms.message_state_to_string(m.state) % tools::get_human_readable_timestamp(m.modified) % get_human_readable_timespan(std::chrono::seconds(now - m.modified)); + if (m.sent == 0) + { + message_writer() << tr("Sent: Never"); + } + else + { + message_writer() << boost::format(tr("Sent: %s, %s ago")) % + tools::get_human_readable_timestamp(m.sent) % get_human_readable_timespan(std::chrono::seconds(now - m.sent)); + } + message_writer() << tr("Authorized signer: ") << ms.signer_to_string(signer, 100); + message_writer() << tr("Content size: ") << m.content.length() << tr(" bytes"); + message_writer() << tr("Content: ") << (display_content ? sanitized_text : tr("(binary data)")); + + if (m.type == mms::message_type::note) + { + // Showing a note and read its text is "processing" it: Set the state accordingly + // which will also delete it from Bitmessage as a side effect + // (Without this little "twist" it would never change the state, and never get deleted) + ms.set_message_processed_or_sent(m.id); + } +} + +void simple_wallet::ask_send_all_ready_messages() +{ + mms::message_store& ms = m_wallet->get_message_store(); + std::vector<mms::message> ready_messages; + const std::vector<mms::message> &messages = ms.get_all_messages(); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + if (m.state == mms::message_state::ready_to_send) + { + ready_messages.push_back(m); + } + } + if (ready_messages.size() != 0) + { + list_mms_messages(ready_messages); + bool send = ms.get_auto_send(); + if (!send) + { + send = user_confirms(tr("Send these messages now?")); + } + if (send) + { + mms::multisig_wallet_state state = get_multisig_wallet_state(); + for (size_t i = 0; i < ready_messages.size(); ++i) + { + ms.send_message(state, ready_messages[i].id); + ms.set_message_processed_or_sent(ready_messages[i].id); + } + success_msg_writer() << tr("Queued for sending."); + } + } +} + +bool simple_wallet::get_message_from_arg(const std::string &arg, mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + bool valid_id = false; + uint32_t id; + try + { + id = (uint32_t)boost::lexical_cast<uint32_t>(arg); + valid_id = ms.get_message_by_id(id, m); + } + catch (const boost::bad_lexical_cast &) + { + } + if (!valid_id) + { + fail_msg_writer() << tr("Invalid message id"); + } + return valid_id; +} + +void simple_wallet::mms_init(const std::vector<std::string> &args) +{ + if (args.size() != 3) + { + fail_msg_writer() << tr("usage: mms init <required_signers>/<authorized_signers> <own_label> <own_transport_address>"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + if (ms.get_active()) + { + if (!user_confirms(tr("The MMS is already initialized. Re-initialize by deleting all signer info and messages?"))) + { + return; + } + } + uint32_t num_required_signers; + uint32_t num_authorized_signers; + const std::string &mn = args[0]; + std::vector<std::string> numbers; + boost::split(numbers, mn, boost::is_any_of("/")); + bool mn_ok = (numbers.size() == 2) + && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100) + && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers); + if (!mn_ok) + { + fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers"); + return; + } + LOCK_IDLE_SCOPE(); + ms.init(get_multisig_wallet_state(), args[1], args[2], num_authorized_signers, num_required_signers); +} + +void simple_wallet::mms_info(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (ms.get_active()) + { + message_writer() << boost::format("The MMS is active for %s/%s multisig.") + % ms.get_num_required_signers() % ms.get_num_authorized_signers(); + } + else + { + message_writer() << tr("The MMS is not active."); + } +} + +void simple_wallet::mms_signer(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + const std::vector<mms::authorized_signer> &signers = ms.get_all_signers(); + if (args.size() == 0) + { + // Without further parameters list all defined signers + list_signers(signers); + return; + } + + uint32_t index; + bool index_valid = get_number_from_arg(args[0], index, 1, ms.get_num_authorized_signers()); + if (index_valid) + { + index--; + } + else + { + fail_msg_writer() << tr("Invalid signer number ") + args[0]; + return; + } + if ((args.size() < 2) || (args.size() > 4)) + { + fail_msg_writer() << tr("mms signer [<number> <label> [<transport_address> [<monero_address>]]]"); + return; + } + + boost::optional<string> label = args[1]; + boost::optional<string> transport_address; + if (args.size() >= 3) + { + transport_address = args[2]; + } + boost::optional<cryptonote::account_public_address> monero_address; + LOCK_IDLE_SCOPE(); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + if (args.size() == 4) + { + cryptonote::address_parse_info info; + bool ok = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[3], oa_prompter); + if (!ok) + { + fail_msg_writer() << tr("Invalid Monero address"); + return; + } + monero_address = info.address; + const std::vector<mms::message> &messages = ms.get_all_messages(); + if ((messages.size() > 0) || state.multisig) + { + fail_msg_writer() << tr("Wallet state does not allow changing Monero addresses anymore"); + return; + } + } + ms.set_signer(state, index, label, transport_address, monero_address); +} + +void simple_wallet::mms_list(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms list"); + return; + } + LOCK_IDLE_SCOPE(); + const std::vector<mms::message> &messages = ms.get_all_messages(); + list_mms_messages(messages); +} + +void simple_wallet::mms_next(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if ((args.size() > 1) || ((args.size() == 1) && (args[0] != "sync"))) + { + fail_msg_writer() << tr("Usage: mms next [sync]"); + return; + } + bool avail = false; + std::vector<mms::processing_data> data_list; + bool force_sync = false; + uint32_t choice = 0; + { + LOCK_IDLE_SCOPE(); + if ((args.size() == 1) && (args[0] == "sync")) + { + // Force the MMS to process any waiting sync info although on its own it would just ignore + // those messages because no need to process them can be seen + force_sync = true; + } + string wait_reason; + { + avail = ms.get_processable_messages(get_multisig_wallet_state(), force_sync, data_list, wait_reason); + } + if (avail) + { + avail = choose_mms_processing(data_list, choice); + } + else if (!wait_reason.empty()) + { + message_writer() << tr("No next step: ") << wait_reason; + } + } + if (avail) + { + mms::processing_data data = data_list[choice]; + bool command_successful = false; + switch(data.processing) + { + case mms::message_processing::prepare_multisig: + message_writer() << tr("prepare_multisig"); + command_successful = prepare_multisig_main(std::vector<std::string>(), true); + break; + + case mms::message_processing::make_multisig: + { + message_writer() << tr("make_multisig"); + size_t number_of_key_sets = data.message_ids.size(); + std::vector<std::string> sig_args(number_of_key_sets + 1); + sig_args[0] = std::to_string(ms.get_num_required_signers()); + for (size_t i = 0; i < number_of_key_sets; ++i) + { + mms::message m = ms.get_message_by_id(data.message_ids[i]); + sig_args[i+1] = m.content; + } + command_successful = make_multisig_main(sig_args, true); + break; + } + + case mms::message_processing::exchange_multisig_keys: + { + message_writer() << tr("exchange_multisig_keys"); + size_t number_of_key_sets = data.message_ids.size(); + // Other than "make_multisig" only the key sets as parameters, no num_required_signers + std::vector<std::string> sig_args(number_of_key_sets); + for (size_t i = 0; i < number_of_key_sets; ++i) + { + mms::message m = ms.get_message_by_id(data.message_ids[i]); + sig_args[i] = m.content; + } + command_successful = exchange_multisig_keys_main(sig_args, true); + break; + } + + case mms::message_processing::create_sync_data: + { + message_writer() << tr("export_multisig_info"); + std::vector<std::string> export_args; + export_args.push_back("MMS"); // dummy filename + command_successful = export_multisig_main(export_args, true); + break; + } + + case mms::message_processing::process_sync_data: + { + message_writer() << tr("import_multisig_info"); + std::vector<std::string> import_args; + for (size_t i = 0; i < data.message_ids.size(); ++i) + { + mms::message m = ms.get_message_by_id(data.message_ids[i]); + import_args.push_back(m.content); + } + command_successful = import_multisig_main(import_args, true); + break; + } + + case mms::message_processing::sign_tx: + { + message_writer() << tr("sign_multisig"); + std::vector<std::string> sign_args; + mms::message m = ms.get_message_by_id(data.message_ids[0]); + sign_args.push_back(m.content); + command_successful = sign_multisig_main(sign_args, true); + break; + } + + case mms::message_processing::submit_tx: + { + message_writer() << tr("submit_multisig"); + std::vector<std::string> submit_args; + mms::message m = ms.get_message_by_id(data.message_ids[0]); + submit_args.push_back(m.content); + command_successful = submit_multisig_main(submit_args, true); + break; + } + + case mms::message_processing::send_tx: + { + message_writer() << tr("Send tx"); + mms::message m = ms.get_message_by_id(data.message_ids[0]); + LOCK_IDLE_SCOPE(); + ms.add_message(get_multisig_wallet_state(), data.receiving_signer_index, m.type, mms::message_direction::out, + m.content); + command_successful = true; + break; + } + + case mms::message_processing::process_signer_config: + { + message_writer() << tr("Process signer config"); + LOCK_IDLE_SCOPE(); + mms::message m = ms.get_message_by_id(data.message_ids[0]); + mms::authorized_signer me = ms.get_signer(0); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + if (!me.auto_config_running) + { + // If no auto-config is running, the config sent may be unsolicited or problematic + // so show what arrived and ask for confirmation before taking it in + std::vector<mms::authorized_signer> signers; + ms.unpack_signer_config(state, m.content, signers); + list_signers(signers); + if (!user_confirms(tr("Replace current signer config with the one displayed above?"))) + { + break; + } + } + ms.process_signer_config(state, m.content); + ms.stop_auto_config(); + list_signers(ms.get_all_signers()); + command_successful = true; + break; + } + + case mms::message_processing::process_auto_config_data: + { + message_writer() << tr("Process auto config data"); + LOCK_IDLE_SCOPE(); + for (size_t i = 0; i < data.message_ids.size(); ++i) + { + ms.process_auto_config_data_message(data.message_ids[i]); + } + ms.stop_auto_config(); + list_signers(ms.get_all_signers()); + add_signer_config_messages(); + command_successful = true; + break; + } + + default: + message_writer() << tr("Nothing ready to process"); + break; + } + + if (command_successful) + { + { + LOCK_IDLE_SCOPE(); + ms.set_messages_processed(data); + ask_send_all_ready_messages(); + } + } + } +} + +void simple_wallet::mms_sync(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms sync"); + return; + } + // Force the start of a new sync round, for exceptional cases where something went wrong + // Can e.g. solve the problem "This signature was made with stale data" after trying to + // create 2 transactions in a row somehow + // Code is identical to the code for 'message_processing::create_sync_data' + message_writer() << tr("export_multisig_info"); + std::vector<std::string> export_args; + export_args.push_back("MMS"); // dummy filename + export_multisig_main(export_args, true); + ask_send_all_ready_messages(); +} + +void simple_wallet::mms_transfer(const std::vector<std::string> &args) +{ + // It's too complicated to check any arguments here, just let 'transfer_main' do the whole job + transfer_main(Transfer, args, true); +} + +void simple_wallet::mms_delete(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms delete (<message_id> | all)"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + if (args[0] == "all") + { + if (user_confirms(tr("Delete all messages?"))) + { + ms.delete_all_messages(); + } + } + else + { + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + // If only a single message and not all delete even if unsent / unprocessed + ms.delete_message(m.id); + } + } +} + +void simple_wallet::mms_send(const std::vector<std::string> &args) +{ + if (args.size() == 0) + { + ask_send_all_ready_messages(); + return; + } + else if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms send [<message_id>]"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + ms.send_message(get_multisig_wallet_state(), m.id); + } +} + +void simple_wallet::mms_receive(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms receive"); + return; + } + std::vector<mms::message> new_messages; + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + bool avail = ms.check_for_messages(get_multisig_wallet_state(), new_messages); + if (avail) + { + list_mms_messages(new_messages); + } +} + +void simple_wallet::mms_export(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms export <message_id>"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + const std::string filename = "mms_message_content"; + if (epee::file_io_utils::save_string_to_file(filename, m.content)) + { + success_msg_writer() << tr("Message content saved to: ") << filename; + } + else + { + fail_msg_writer() << tr("Failed to to save message content"); + } + } +} + +void simple_wallet::mms_note(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() == 0) + { + LOCK_IDLE_SCOPE(); + const std::vector<mms::message> &messages = ms.get_all_messages(); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + if ((m.type == mms::message_type::note) && (m.state == mms::message_state::waiting)) + { + show_message(m); + } + } + return; + } + if (args.size() < 2) + { + fail_msg_writer() << tr("Usage: mms note [<label> <text>]"); + return; + } + uint32_t signer_index; + bool found = ms.get_signer_index_by_label(args[0], signer_index); + if (!found) + { + fail_msg_writer() << tr("No signer found with label ") << args[0]; + return; + } + std::string note = ""; + for (size_t n = 1; n < args.size(); ++n) + { + if (n > 1) + { + note += " "; + } + note += args[n]; + } + LOCK_IDLE_SCOPE(); + ms.add_message(get_multisig_wallet_state(), signer_index, mms::message_type::note, + mms::message_direction::out, note); + ask_send_all_ready_messages(); +} + +void simple_wallet::mms_show(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms show <message_id>"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + show_message(m); + } +} + +void simple_wallet::mms_set(const std::vector<std::string> &args) +{ + bool set = args.size() == 2; + bool query = args.size() == 1; + if (!set && !query) + { + fail_msg_writer() << tr("Usage: mms set <option_name> [<option_value>]"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + LOCK_IDLE_SCOPE(); + if (args[0] == "auto-send") + { + if (set) + { + bool result; + bool ok = parse_bool(args[1], result); + if (ok) + { + ms.set_auto_send(result); + } + else + { + fail_msg_writer() << tr("Wrong option value"); + } + } + else + { + message_writer() << (ms.get_auto_send() ? tr("Auto-send is on") : tr("Auto-send is off")); + } + } + else + { + fail_msg_writer() << tr("Unknown option"); + } +} + +void simple_wallet::mms_help(const std::vector<std::string> &args) +{ + if (args.size() > 1) + { + fail_msg_writer() << tr("Usage: mms help [<subcommand>]"); + return; + } + std::vector<std::string> help_args; + help_args.push_back("mms"); + if (args.size() == 1) + { + help_args.push_back(args[0]); + } + help(help_args); +} + +void simple_wallet::mms_send_signer_config(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms send_signer_config"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + if (!ms.signer_config_complete()) + { + fail_msg_writer() << tr("Signer config not yet complete"); + return; + } + LOCK_IDLE_SCOPE(); + add_signer_config_messages(); + ask_send_all_ready_messages(); +} + +void simple_wallet::mms_start_auto_config(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + uint32_t other_signers = ms.get_num_authorized_signers() - 1; + size_t args_size = args.size(); + if ((args_size != 0) && (args_size != other_signers)) + { + fail_msg_writer() << tr("Usage: mms start_auto_config [<label> <label> ...]"); + return; + } + if ((args_size == 0) && !ms.signer_labels_complete()) + { + fail_msg_writer() << tr("There are signers without a label set. Complete labels before auto-config or specify them as parameters here."); + return; + } + mms::authorized_signer me = ms.get_signer(0); + if (me.auto_config_running) + { + if (!user_confirms(tr("Auto-config is already running. Cancel and restart?"))) + { + return; + } + } + LOCK_IDLE_SCOPE(); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + if (args_size != 0) + { + // Set (or overwrite) all the labels except "me" from the arguments + for (uint32_t i = 1; i < (other_signers + 1); ++i) + { + ms.set_signer(state, i, args[i - 1], boost::none, boost::none); + } + } + ms.start_auto_config(state); + // List the signers to show the generated auto-config tokens + list_signers(ms.get_all_signers()); +} + +void simple_wallet::mms_stop_auto_config(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms stop_auto_config"); + return; + } + if (!user_confirms(tr("Delete any auto-config tokens and stop auto-config?"))) + { + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + LOCK_IDLE_SCOPE(); + ms.stop_auto_config(); +} + +void simple_wallet::mms_auto_config(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms auto_config <auto_config_token>"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + std::string adjusted_token; + if (!ms.check_auto_config_token(args[0], adjusted_token)) + { + fail_msg_writer() << tr("Invalid auto-config token"); + return; + } + mms::authorized_signer me = ms.get_signer(0); + if (me.auto_config_running) + { + if (!user_confirms(tr("Auto-config already running. Cancel and restart?"))) + { + return; + } + } + LOCK_IDLE_SCOPE(); + ms.add_auto_config_data_message(get_multisig_wallet_state(), adjusted_token); + ask_send_all_ready_messages(); +} + +bool simple_wallet::mms(const std::vector<std::string> &args) +{ + try + { + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() == 0) + { + mms_info(args); + return true; + } + + const std::string &sub_command = args[0]; + std::vector<std::string> mms_args = args; + mms_args.erase(mms_args.begin()); + + if (sub_command == "init") + { + mms_init(mms_args); + return true; + } + if (!ms.get_active()) + { + fail_msg_writer() << tr("The MMS is not active. Activate using the \"mms init\" command"); + return true; + } + else if (sub_command == "info") + { + mms_info(mms_args); + } + else if (sub_command == "signer") + { + mms_signer(mms_args); + } + else if (sub_command == "list") + { + mms_list(mms_args); + } + else if (sub_command == "next") + { + mms_next(mms_args); + } + else if (sub_command == "sync") + { + mms_sync(mms_args); + } + else if (sub_command == "transfer") + { + mms_transfer(mms_args); + } + else if (sub_command == "delete") + { + mms_delete(mms_args); + } + else if (sub_command == "send") + { + mms_send(mms_args); + } + else if (sub_command == "receive") + { + mms_receive(mms_args); + } + else if (sub_command == "export") + { + mms_export(mms_args); + } + else if (sub_command == "note") + { + mms_note(mms_args); + } + else if (sub_command == "show") + { + mms_show(mms_args); + } + else if (sub_command == "set") + { + mms_set(mms_args); + } + else if (sub_command == "help") + { + mms_help(mms_args); + } + else if (sub_command == "send_signer_config") + { + mms_send_signer_config(mms_args); + } + else if (sub_command == "start_auto_config") + { + mms_start_auto_config(mms_args); + } + else if (sub_command == "stop_auto_config") + { + mms_stop_auto_config(mms_args); + } + else if (sub_command == "auto_config") + { + mms_auto_config(mms_args); + } + else + { + fail_msg_writer() << tr("Invalid MMS subcommand"); + } + } + catch (const tools::error::no_connection_to_daemon &e) + { + fail_msg_writer() << tr("Error in MMS command: ") << e.what() << " " << e.request(); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error in MMS command: ") << e.what(); + PRINT_USAGE(USAGE_MMS); + return true; + } + return true; +} +// End MMS ------------------------------------------------------------------------------------------------ + |