diff --git a/src/daemon/monero_daemon_model.cpp b/src/daemon/monero_daemon_model.cpp index 28047b6..475a46a 100644 --- a/src/daemon/monero_daemon_model.cpp +++ b/src/daemon/monero_daemon_model.cpp @@ -275,7 +275,7 @@ namespace monero { // set bool values if (m_is_miner_tx != boost::none) monero_utils::addJsonMember("isMinerTx", m_is_miner_tx.get(), allocator, root); - if (m_do_not_relay != boost::none) monero_utils::addJsonMember("doNotRelay", m_do_not_relay.get(), allocator, root); + if (m_relay != boost::none) monero_utils::addJsonMember("relay", m_relay.get(), allocator, root); if (m_is_relayed != boost::none) monero_utils::addJsonMember("isRelayed", m_is_relayed.get(), allocator, root); if (m_is_confirmed != boost::none) monero_utils::addJsonMember("isConfirmed", m_is_confirmed.get(), allocator, root); if (m_in_tx_pool != boost::none) monero_utils::addJsonMember("inTxPool", m_in_tx_pool.get(), allocator, root); @@ -305,7 +305,7 @@ namespace monero { else if (key == string("paymentId")) tx->m_payment_id = it->second.data(); else if (key == string("fee")) tx->m_fee = it->second.get_value(); else if (key == string("mixin")) throw runtime_error("mixin deserialization not implemented"); - else if (key == string("doNotRelay")) tx->m_do_not_relay = it->second.get_value(); + else if (key == string("relay")) tx->m_relay = it->second.get_value(); else if (key == string("isRelayed")) tx->m_is_relayed = it->second.get_value(); else if (key == string("isConfirmed")) tx->m_is_confirmed = it->second.get_value(); else if (key == string("inTxPool")) tx->m_in_tx_pool = it->second.get_value(); @@ -347,7 +347,7 @@ namespace monero { tgt->m_payment_id = src->m_payment_id; tgt->m_fee = src->m_fee; tgt->m_ring_size = src->m_ring_size; - tgt->m_do_not_relay = src->m_do_not_relay; + tgt->m_relay = src->m_relay; tgt->m_is_relayed = src->m_is_relayed; tgt->m_is_confirmed = src->m_is_confirmed; tgt->m_in_tx_pool = src->m_in_tx_pool; @@ -427,7 +427,7 @@ namespace monero { m_fee = gen_utils::reconcile(m_fee, other->m_fee, "tx fee"); m_ring_size = gen_utils::reconcile(m_ring_size, other->m_ring_size, "tx m_ring_size"); m_is_confirmed = gen_utils::reconcile(m_is_confirmed, other->m_is_confirmed); - m_do_not_relay = gen_utils::reconcile(m_do_not_relay, other->m_do_not_relay); + m_relay = gen_utils::reconcile(m_relay, other->m_relay); m_is_relayed = gen_utils::reconcile(m_is_relayed, other->m_is_relayed); m_is_double_spend_seen = gen_utils::reconcile(m_is_double_spend_seen, other->m_is_double_spend_seen); m_key = gen_utils::reconcile(m_key, other->m_key); diff --git a/src/daemon/monero_daemon_model.h b/src/daemon/monero_daemon_model.h index d5bc992..565bc8d 100644 --- a/src/daemon/monero_daemon_model.h +++ b/src/daemon/monero_daemon_model.h @@ -176,7 +176,7 @@ namespace monero { boost::optional m_payment_id; boost::optional m_fee; boost::optional m_ring_size; - boost::optional m_do_not_relay; + boost::optional m_relay; boost::optional m_is_relayed; boost::optional m_is_confirmed; boost::optional m_in_tx_pool; diff --git a/src/wallet/monero_wallet.h b/src/wallet/monero_wallet.h index 1a459a9..6eda7cc 100644 --- a/src/wallet/monero_wallet.h +++ b/src/wallet/monero_wallet.h @@ -763,54 +763,58 @@ namespace monero { } /** - * Create a transaction to transfer funds from this wallet according to the - * given config. The transaction may be relayed later. + * Create a transaction to transfer funds from this wallet. * * @param config configures the transaction to create - * @return a tx set for the requested transaction if possible + * @return the created transaction */ - virtual monero_tx_set create_tx(monero_tx_config& config) { - throw runtime_error("create_tx() not supported"); + virtual shared_ptr create_tx(const monero_tx_config& config) { + if (config.m_can_split != boost::none && config.m_can_split.get()) throw runtime_error("Cannot split transactions with create_tx(); use create_txs() instead"); + monero_tx_config config_copy = monero_tx_config(config); + config_copy.m_can_split = false; + return create_txs(config_copy)[0]; } /** - * Create a transaction to transfers funds from this wallet to a destination address. - * The transaction may be relayed later. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @return a tx set for the requested transaction if possible - */ - virtual monero_tx_set create_tx(uint32_t account_index, string address, uint64_t amount) { - throw runtime_error("create_tx() not supported"); - } - - /** - * Create a transaction to transfers funds from this wallet to a destination address. - * The transaction may be relayed later. - * - * @param accountIndex is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @param priority is the send priority (default normal) - * @return a tx set for the requested transaction if possible - */ - virtual monero_tx_set create_tx(int account_index, string address, uint64_t amount, monero_tx_priority priority) { - throw runtime_error("create_tx() not supported"); - } - - /** - * Create one or more transactions to transfer funds from this wallet - * according to the given config. The transactions may later be relayed. + * Create one or more transactions to transfer funds from this wallet. * * @param config configures the transactions to create - * @return a tx set for the requested transactions if possible + * @return the created transactions */ - virtual monero_tx_set create_txs(monero_tx_config& config) { + virtual vector> create_txs(const monero_tx_config& config) { throw runtime_error("create_txs() not supported"); } + /** + * Sweep unlocked funds according to the given config. + * + * @param config is the sweep configuration + * @return the created transactions + */ + virtual vector> sweep_unlocked(const monero_tx_config& config) { + throw runtime_error("sweep_unlocked() not supported"); + } + + /** + * Sweep an output with a given key image. + * + * @param config configures the sweep transaction + * @return the created transaction + */ + virtual shared_ptr sweep_output(const monero_tx_config& config) { + throw runtime_error("sweep_output() not supported"); + } + + /** + * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. + * + * @param relay specifies if the resulting transaction should be relayed (default false) + * @return the created transactions + */ + virtual vector> sweep_dust(bool relay = false) { + throw runtime_error("sweep_dust() not supported"); + } + /** * Relay a transaction previously created without relaying. * @@ -818,7 +822,9 @@ namespace monero { * @return string is the hash of the relayed tx */ virtual string relay_tx(const string& tx_metadata) { - throw runtime_error("relay_tx() not supported"); + vector tx_metadatas; + tx_metadatas.push_back(tx_metadata); + return relay_txs(tx_metadatas)[0]; } /** @@ -828,7 +834,19 @@ namespace monero { * @return the hash of the relayed tx */ virtual string relay_tx(const monero_tx_wallet& tx) { - throw runtime_error("relay_tx() not supported"); + return relay_tx(tx.m_metadata.get()); + } + + /** + * Relay previously created transactions. + * + * @param txs are the transactions to relay + * @return the hashes of the relayed txs + */ + virtual vector relay_txs(const vector>& txs) { + vector tx_hexes; + for (const shared_ptr& tx : txs) tx_hexes.push_back(tx->m_metadata.get()); + return relay_txs(tx_hexes); } /** @@ -841,95 +859,6 @@ namespace monero { throw runtime_error("relay_txs() not supported"); } - /** - * Relay previously created transactions. - * - * @param txs are the transactions to relay - * @return the hashes of the relayed txs - */ - virtual vector relay_txs(const vector>& txs) { - throw runtime_error("relay_txs() not supported"); - } - - /** - * Create and relay a transaction to transfer funds from this wallet - * according to the given config. - * - * @param config configures the transaction - * @return a tx set with the requested transaction if possible - */ - virtual monero_tx_set send_tx(const monero_tx_config& config) { - throw runtime_error("send_tx() not supported"); - } - - /** - * Create and relay a transaction to transfers funds from this wallet to - * a destination address. - * - * @param account_index is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @return a tx set with the requested transaction if possible - */ - virtual monero_tx_set send_tx(uint32_t account_index, string address, uint64_t amount) { - throw runtime_error("send_tx() not supported"); - } - - /** - * Create and relay a transaction to transfers funds from this wallet to - * a destination address. - * - * @param account_index is the index of the account to withdraw funds from - * @param address is the destination address to send funds to - * @param amount is the amount being sent - * @param priority is the send priority (default normal) - * @return a tx set with the requested transaction if possible - */ - virtual monero_tx_set send_tx(uint32_t account_index, string address, uint64_t amount, monero_tx_priority priority) { - throw runtime_error("send_tx() not supported"); - } - - /** - * Create one or more transactions which transfer funds from this wallet to - * one or more destinations depending on the given configuration. - * - * @param config configures the transaction - * @return a tx set with the requested transactions if possible - */ - virtual monero_tx_set send_txs(const monero_tx_config& config) { - throw runtime_error("send_txs() not supported"); - } - - /** - * Sweep unlocked funds according to the given config. - * - * @param config is the sweep configuration - * @return the tx sets with the requested transactions - */ - virtual vector sweep_unlocked(const monero_tx_config& config) { - throw runtime_error("sweep_unlocked() not supported"); - } - - /** - * Sweep an output with a given key image. - * - * @param config configures the sweep transaction - * @return a tx set with the requested transaction if possible - */ - virtual monero_tx_set sweep_output(const monero_tx_config& config) { - throw runtime_error("sweep_output() not supported"); - } - - /** - * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. - * - * @param do_not_relay specifies if the resulting transaction should not be relayed (defaults to false i.e. relayed) - * @return a tx set with the requested transactions if possible - */ - virtual monero_tx_set sweep_dust(bool do_not_relay = false) { - throw runtime_error("sweep_dust() not supported"); - } - /** * Parses a tx set containing unsigned or multisig tx hex to a new tx set containing structured transactions. * diff --git a/src/wallet/monero_wallet_core.cpp b/src/wallet/monero_wallet_core.cpp index b2f8787..c7d66be 100644 --- a/src/wallet/monero_wallet_core.cpp +++ b/src/wallet/monero_wallet_core.cpp @@ -112,7 +112,7 @@ namespace monero { tx->m_is_failed = false; tx->m_is_relayed = true; tx->m_in_tx_pool = false; - tx->m_do_not_relay = false; + tx->m_relay = true; tx->m_is_double_spend_seen = false; // compute m_num_confirmations TODO monero core: this logic is based on wallet_rpc_server.cpp:87 but it should be encapsulated in wallet2 @@ -165,7 +165,7 @@ namespace monero { tx->m_is_failed = false; tx->m_is_relayed = true; tx->m_in_tx_pool = false; - tx->m_do_not_relay = false; + tx->m_relay = true; tx->m_is_double_spend_seen = false; // compute m_num_confirmations TODO monero core: this logic is based on wallet_rpc_server.cpp:87 but it should be encapsulated in wallet2 @@ -233,7 +233,7 @@ namespace monero { tx->m_is_failed = false; tx->m_is_relayed = true; tx->m_in_tx_pool = true; - tx->m_do_not_relay = false; + tx->m_relay = true; tx->m_is_double_spend_seen = ppd.m_double_spend_seen; tx->m_num_confirmations = 0; @@ -274,7 +274,7 @@ namespace monero { tx->m_is_confirmed = false; tx->m_is_relayed = !tx->m_is_failed.get(); tx->m_in_tx_pool = !tx->m_is_failed.get(); - tx->m_do_not_relay = false; + tx->m_relay = true; if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true tx->m_num_confirmations = 0; @@ -333,7 +333,7 @@ namespace monero { tx->m_is_failed = false; tx->m_is_relayed = true; tx->m_in_tx_pool = false; - tx->m_do_not_relay = false; + tx->m_relay = true; tx->m_is_double_spend_seen = false; tx->m_is_locked = !m_w2.is_transfer_unlocked(td); @@ -1768,49 +1768,513 @@ namespace monero { return result; } - monero_tx_set monero_wallet_core::create_tx(monero_tx_config& config) { - if (config.m_can_split != boost::none && config.m_can_split.get()) throw runtime_error("Cannot request split transactions with create_tx() which prevents splitting; use create_txs() instead"); - config.m_can_split = false; - return create_txs(config); + vector> monero_wallet_core::create_txs(const monero_tx_config& config) { + MTRACE("monero_wallet_core::create_txs"); + //cout << "monero_tx_config: " << config.serialize() << endl; + + // validate config + if (config.m_account_index == boost::none) throw runtime_error("Must specify account index to send from"); + + // prepare parameters for wallet rpc's validate_transfer() + string payment_id = config.m_payment_id == boost::none ? string("") : config.m_payment_id.get(); + list tr_destinations; + for (const shared_ptr& destination : config.get_normalized_destinations()) { + tools::wallet_rpc::transfer_destination tr_destination; + tr_destination.amount = destination->m_amount.get(); + tr_destination.address = destination->m_address.get(); + tr_destinations.push_back(tr_destination); + } + + // validate the requested txs and populate dsts & extra + std::vector dsts; + std::vector extra; + epee::json_rpc::error err; + if (!validate_transfer(m_w2.get(), tr_destinations, payment_id, dsts, extra, true, err)) { + throw runtime_error(err.message); + } + + // prepare parameters for wallet2's create_transactions_2() + uint64_t mixin = m_w2->adjust_mixin(0); // get mixin for call to 'create_transactions_2' + uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); + uint64_t unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); + uint32_t account_index = config.m_account_index.get(); + std::set subaddress_indices; + for (const uint32_t& subaddress_idx : config.m_subaddress_indices) subaddress_indices.insert(subaddress_idx); + + // prepare transactions + vector ptx_vector = m_w2->create_transactions_2(dsts, mixin, unlock_time, priority, extra, account_index, subaddress_indices); + if (ptx_vector.empty()) throw runtime_error("No transaction created"); + + // check if request cannot be fulfilled due to splitting + if (config.m_can_split != boost::none && config.m_can_split.get() == false && ptx_vector.size() != 1) { + throw runtime_error("Transaction would be too large. Try send_txs()"); + } + + // config for fill_response() + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + bool relay = config.m_relay != boost::none && config.m_relay.get(); + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + list tx_keys; + list tx_amounts; + list tx_fees; + string multisig_tx_hex; + string unsigned_tx_hex; + list tx_hashes; + list tx_blobs; + list tx_metadatas; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, err)) { + throw runtime_error("need to handle error filling response!"); // TODO + } + + // build sent txs from results // TODO: break this into separate utility function + vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + shared_ptr tx = make_shared(); + txs.push_back(tx); + tx->m_hash = *tx_hashes_iter; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_full_hex = *tx_blobs_iter; + tx->m_metadata = *tx_metadatas_iter; + shared_ptr out_transfer = make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_amount = *tx_amounts_iter; + + // init other known fields + tx->m_is_outgoing = true; + tx->m_payment_id = config.m_payment_id; + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = config.m_relay != boost::none && config.m_relay.get(); + tx->m_is_relayed = tx->m_relay.get(); + tx->m_in_tx_pool = tx->m_relay.get(); + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); + tx->m_is_locked = true; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 + out_transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here + out_transfer->m_destinations = config.get_normalized_destinations(); + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + } + + // build tx set and return txs + shared_ptr tx_set = make_shared(); + tx_set->m_txs = txs; + for (int i = 0; i < txs.size(); i++) txs[i]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + return txs; } - monero_tx_set monero_wallet_core::create_tx(uint32_t account_index, string address, uint64_t amount) { - return create_tx(account_index, address, amount, monero_tx_priority::NORMAL); + vector> monero_wallet_core::sweep_unlocked(const monero_tx_config& config) { + + // validate config + vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw runtime_error("Must specify exactly one destination to sweep to"); + if (destinations[0]->m_address == boost::none) throw runtime_error("Must specify destination address to sweep to"); + if (destinations[0]->m_amount != boost::none) throw runtime_error("Cannot specify amount to sweep"); + if (config.m_account_index == boost::none && config.m_subaddress_indices.size() != 0) throw runtime_error("Must specify account index if subaddress indices are specified"); + + // determine account and subaddress indices to sweep; default to all with unlocked balance if not specified + std::map> indices; + if (config.m_account_index != boost::none) { + if (config.m_subaddress_indices.size() != 0) { + indices[config.m_account_index.get()] = config.m_subaddress_indices; + } else { + vector subaddress_indices; + for (const monero_subaddress& subaddress : monero_wallet::get_subaddresses(config.m_account_index.get())) { + if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); + } + indices[config.m_account_index.get()] = subaddress_indices; + } + } else { + vector accounts = monero_wallet::get_accounts(true); + for (const monero_account& account : accounts) { + if (account.m_unlocked_balance.get() > 0) { + vector subaddress_indices; + for (const monero_subaddress& subaddress : account.m_subaddresses) { + if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); + } + indices[account.m_index.get()] = subaddress_indices; + } + } + } + + // sweep from each account and collect resulting txs + vector> txs; + for (pair> subaddress_indices_pair : indices) { + + // copy and modify the original config + monero_tx_config copy = config.copy(); + copy.m_account_index = subaddress_indices_pair.first; + copy.m_sweep_each_subaddress = false; + + // sweep all subaddresses together // TODO monero core: can this reveal outputs belong to the same wallet? + if (copy.m_sweep_each_subaddress == boost::none || copy.m_sweep_each_subaddress.get() != true) { + copy.m_subaddress_indices = subaddress_indices_pair.second; + vector> account_txs = sweep_account(copy); + txs.insert(std::end(txs), std::begin(account_txs), std::end(account_txs)); + } + + // otherwise sweep each subaddress individually + else { + for (uint32_t subaddress_index : subaddress_indices_pair.second) { + vector subaddress_indices; + subaddress_indices.push_back(subaddress_index); + copy.m_subaddress_indices = subaddress_indices; + vector> account_txs = sweep_account(copy); + txs.insert(std::end(txs), std::begin(account_txs), std::end(account_txs)); + } + } + } + + // return sweep txs + return txs; } - monero_tx_set monero_wallet_core::create_tx(int account_index, string address, uint64_t amount, monero_tx_priority priority) { - monero_tx_config config; - config.m_account_index = account_index; - config.m_destinations.push_back(make_shared(address, amount)); - config.m_priority = priority; - return create_tx(config); + vector> monero_wallet_core::sweep_account(const monero_tx_config& config) { + + // validate config + vector> destinations = config.get_normalized_destinations(); + if (config.m_account_index == boost::none) throw runtime_error("Must specify account index to sweep from"); + if (destinations.size() != 1 || destinations[0]->m_address == boost::none || destinations[0]->m_address.get().empty()) throw runtime_error("Must provide exactly one destination address to sweep output to"); + if (destinations[0]->m_amount != boost::none) throw runtime_error("Cannot specify destination amount to sweep"); + if (config.m_key_image != boost::none) throw runtime_error("Cannot define key image in sweep_account(); use sweep_output() to sweep an output by its key image"); + if (config.m_sweep_each_subaddress != boost::none && config.m_sweep_each_subaddress.get() == true) throw runtime_error("Cannot sweep each subaddress individually with sweep_account"); + + // validate the transfer requested and populate dsts & extra + std::list destination; + destination.push_back(wallet_rpc::transfer_destination()); + destination.back().amount = 0; + destination.back().address = destinations[0]->m_address.get(); + string payment_id = config.m_payment_id == boost::none ? string("") : config.m_payment_id.get(); + std::vector dsts; + std::vector extra; + epee::json_rpc::error err; + if (!validate_transfer(m_w2.get(), destination, payment_id, dsts, extra, true, err)) { + throw runtime_error("Failed to validate sweep_account transfer request"); + } + + // TODO monero-core: this is default `outputs` in COMMAND_RPC_SWEEP_ALL which is not documented + uint64_t num_outputs = 1; + + // prepare parameters for wallet2's create_transactions_all() + uint64_t below_amount = config.m_below_amount == boost::none ? 0 : config.m_below_amount.get(); + uint64_t mixin = m_w2->adjust_mixin(0); + uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); + uint64_t unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); + uint32_t account_index = config.m_account_index.get(); + std::set subaddress_indices; + for (const uint32_t& subaddress_idx : config.m_subaddress_indices) subaddress_indices.insert(subaddress_idx); + + // prepare transactions + std::vector ptx_vector = m_w2->create_transactions_all(below_amount, dsts[0].addr, dsts[0].is_subaddress, num_outputs, mixin, unlock_time, priority, extra, account_index, subaddress_indices); + + // config for fill_response() + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + bool relay = config.m_relay != boost::none && config.m_relay.get(); + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + list tx_keys; + list tx_amounts; + list tx_fees; + string multisig_tx_hex; + string unsigned_tx_hex; + list tx_hashes; + list tx_blobs; + list tx_metadatas; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, err)) { + throw runtime_error("need to handle error filling response!"); // TODO + } + + // build sent txs from results // TODO: break this into separate utility function + vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + shared_ptr tx = make_shared(); + txs.push_back(tx); + tx->m_hash = *tx_hashes_iter; + tx->m_is_locked = true; + tx->m_is_outgoing = true; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_full_hex = *tx_blobs_iter; + tx->m_metadata = *tx_metadatas_iter; + shared_ptr out_transfer = make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_amount = *tx_amounts_iter; + shared_ptr destination = make_shared(destinations[0]->m_address.get(), out_transfer->m_amount.get()); + out_transfer->m_destinations.push_back(destination); + + // init other known fields + tx->m_payment_id = config.m_payment_id; + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 + out_transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + } + + // link tx set and return + shared_ptr tx_set; + tx_set->m_txs = txs; + for (int i = 0; i < txs.size(); i++) txs[i]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + return txs; } - monero_tx_set monero_wallet_core::create_txs(monero_tx_config& config) { + shared_ptr monero_wallet_core::sweep_output(const monero_tx_config& config) { + MTRACE("sweep_output()"); + //MTRACE("monero_tx_config: " << config.serialize()); - // modify config to not relay - boost::optional requested_do_not_relay = config.m_do_not_relay; - config.m_do_not_relay = true; + // validate input config + vector> destinations = config.get_normalized_destinations(); + if (config.m_key_image == boost::none || config.m_key_image.get().empty()) throw runtime_error("Must provide key image of output to sweep"); + if (destinations.size() != 1 || destinations[0]->m_address == boost::none || destinations[0]->m_address.get().empty()) throw runtime_error("Must provide exactly one destination address to sweep output to"); - // invoke common method which doesn't relay - monero_tx_set tx_set = send_txs(config); + // validate the transfer queried and populate dsts & extra + string m_payment_id = config.m_payment_id == boost::none ? string("") : config.m_payment_id.get(); + std::vector dsts; + std::vector extra; + std::list destination; + destination.push_back(wallet_rpc::transfer_destination()); + destination.back().amount = 0; + destination.back().address = destinations[0]->m_address.get(); + epee::json_rpc::error err; + if (!validate_transfer(m_w2.get(), destination, m_payment_id, dsts, extra, true, err)) { + //throw runtime_error(er); // TODO + throw runtime_error("Handle validate_transfer error!"); + } - // restore doNotRelay of config and txs - config.m_do_not_relay = requested_do_not_relay; - for (const shared_ptr& tx : tx_set.m_txs) tx->m_do_not_relay = requested_do_not_relay; + // validate key image + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(config.m_key_image.get(), ki)) { + throw runtime_error("failed to parse key image"); + } - // return results - return tx_set; + // create transaction + uint64_t mixin = m_w2->adjust_mixin(0); + uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); + uint64_t unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); + std::vector ptx_vector = m_w2->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, 1, mixin, unlock_time, priority, extra); + + // validate created transaction + if (ptx_vector.empty()) throw runtime_error("No outputs found"); + if (ptx_vector.size() > 1) throw runtime_error("Multiple transactions are created, which is not supposed to happen"); + const wallet2::pending_tx &ptx = ptx_vector[0]; + if (ptx.selected_transfers.size() > 1) throw runtime_error("The transaction uses multiple inputs, which is not supposed to happen"); + + // config for fill_response() + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + bool relay = config.m_relay != boost::none && config.m_relay.get(); + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + list tx_keys; + list tx_amounts; + list tx_fees; + string multisig_tx_hex; + string unsigned_tx_hex; + list tx_hashes; + list tx_blobs; + list tx_metadatas; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, err)) { + throw runtime_error("need to handle error filling response!"); // TODO: return err message + } + + // build sent txs from results // TODO: use common utility with send_txs() to avoid code duplication + vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + shared_ptr tx = make_shared(); + txs.push_back(tx); + tx->m_hash = *tx_hashes_iter; + tx->m_is_outgoing = true; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_full_hex = *tx_blobs_iter; + tx->m_metadata = *tx_metadatas_iter; + shared_ptr out_transfer = make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_amount = *tx_amounts_iter; + + // init other known fields + tx->m_payment_id = config.m_payment_id; + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); + tx->m_is_locked = true; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 + out_transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here + out_transfer->m_destinations = destinations; + out_transfer->m_destinations[0]->m_amount = *tx_amounts_iter; + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + } + + // validate one transaction + if (txs.size() != 1) throw runtime_error("Expected 1 transaction but was " + boost::lexical_cast(txs.size())); + + // link tx set and return + shared_ptr tx_set = make_shared(); + tx_set->m_txs = txs; + txs[0]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + return txs[0]; } - string monero_wallet_core::relay_tx(const string& tx_metadata) { - vector tx_metadatas; - tx_metadatas.push_back(tx_metadata); - return relay_txs(tx_metadatas)[0]; - } + vector> monero_wallet_core::sweep_dust(bool relay) { + MTRACE("monero_wallet_core::sweep_dust()"); - string monero_wallet_core::relay_tx(const monero_tx_wallet& tx) { - return relay_tx(tx.m_metadata.get()); + // create transaction to fill + std::vector ptx_vector = m_w2->create_unmixable_sweep_transactions(); + + // config for fill_response + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + list tx_keys; + list tx_amounts; + list tx_fees; + string multisig_tx_hex; + string unsigned_tx_hex; + list tx_hashes; + list tx_blobs; + list tx_metadatas; + epee::json_rpc::error er; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, er)) { + throw runtime_error("need to handle error filling response!"); // TODO: return err message + } + + // build sent txs from results // TODO: use common utility with send_txs() to avoid code duplication + vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + shared_ptr tx = make_shared(); + txs.push_back(tx); + tx->m_hash = *tx_hashes_iter; + tx->m_is_outgoing = true; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_full_hex = *tx_blobs_iter; + tx->m_metadata = *tx_metadatas_iter; + shared_ptr out_transfer = make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_amount = *tx_amounts_iter; + + // init other known fields + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = 0; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 + out_transfer->m_destinations[0]->m_amount = *tx_amounts_iter; + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + } + + // throw exception if no dust to sweep (dust only exists pre-rct) + if (txs.empty() && multisig_tx_hex.empty() && unsigned_tx_hex.empty()) throw runtime_error("No dust to sweep"); + + // link tx set and return + shared_ptr tx_set; + tx_set->m_txs = txs; + for (int i = 0; i < txs.size(); i++) txs[i]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + return txs; } vector monero_wallet_core::relay_txs(const vector& tx_metadatas) { @@ -1851,530 +2315,6 @@ namespace monero { return tx_hashes; } - vector monero_wallet_core::relay_txs(const vector>& txs) { - vector tx_hexes; - for (const shared_ptr& tx : txs) tx_hexes.push_back(tx->m_metadata.get()); - return relay_txs(tx_hexes); - } - - monero_tx_set monero_wallet_core::send_tx(const monero_tx_config& config) { - if (config.m_can_split != boost::none && config.m_can_split.get()) throw runtime_error("Cannot request split transactions with send_tx() which prevents splitting; use sendSplit() instead"); - monero_tx_config copy = config.copy(); - copy.m_can_split = false; - return send_txs(copy); - } - - monero_tx_set monero_wallet_core::send_tx(uint32_t account_index, string address, uint64_t amount) { - return send_tx(account_index, address, amount, monero_tx_priority::NORMAL); - } - - monero_tx_set monero_wallet_core::send_tx(uint32_t account_index, string address, uint64_t amount, monero_tx_priority priority) { - monero_tx_config config; - config.m_account_index = account_index; - config.m_destinations.push_back(make_shared(address, amount)); - config.m_priority = priority; - return send_tx(config); - } - - monero_tx_set monero_wallet_core::send_txs(const monero_tx_config& config) { - MTRACE("monero_wallet_core::send_txs(config)"); - //MTRACE("monero_tx_config: " << config.serialize()); - - // validate config - if (config.m_account_index == boost::none) throw runtime_error("Must specify account index to send from"); - - // prepare parameters for wallet rpc's validate_transfer() - string payment_id = config.m_payment_id == boost::none ? string("") : config.m_payment_id.get(); - list tr_destinations; - for (const shared_ptr& destination : config.m_destinations) { - tools::wallet_rpc::transfer_destination tr_destination; - tr_destination.amount = destination->m_amount.get(); - tr_destination.address = destination->m_address.get(); - tr_destinations.push_back(tr_destination); - } - - // validate the requested txs and populate dsts & extra - std::vector dsts; - std::vector extra; - epee::json_rpc::error err; - if (!validate_transfer(m_w2.get(), tr_destinations, payment_id, dsts, extra, true, err)) { - throw runtime_error(err.message); - } - - // prepare parameters for wallet2's create_transactions_2() - uint64_t mixin = m_w2->adjust_mixin(0); // get mixin for call to 'create_transactions_2' - uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); - uint64_t unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); - uint32_t account_index = config.m_account_index.get(); - std::set subaddress_indices; - for (const uint32_t& subaddress_idx : config.m_subaddress_indices) subaddress_indices.insert(subaddress_idx); - - // prepare transactions - vector ptx_vector = m_w2->create_transactions_2(dsts, mixin, unlock_time, priority, extra, account_index, subaddress_indices); - if (ptx_vector.empty()) throw runtime_error("No transaction created"); - - // check if request cannot be fulfilled due to splitting - if (config.m_can_split != boost::none && config.m_can_split.get() == false && ptx_vector.size() != 1) { - throw runtime_error("Transaction would be too large. Try send_txs()"); - } - - // config for fill_response() - bool get_tx_keys = true; - bool get_tx_hex = true; - bool get_tx_metadata = true; - bool m_do_not_relay = config.m_do_not_relay == boost::none ? false : config.m_do_not_relay.get(); - - // commit txs (if relaying) and get response using wallet rpc's fill_response() - list tx_keys; - list tx_amounts; - list tx_fees; - string multisig_tx_hex; - string unsigned_tx_hex; - list tx_hashes; - list tx_blobs; - list tx_metadatas; - if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, m_do_not_relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, err)) { - throw runtime_error("need to handle error filling response!"); // TODO - } - - // build sent txs from results // TODO: break this into separate utility function - vector> txs; - auto tx_hashes_iter = tx_hashes.begin(); - auto tx_keys_iter = tx_keys.begin(); - auto tx_amounts_iter = tx_amounts.begin(); - auto tx_fees_iter = tx_fees.begin(); - auto tx_blobs_iter = tx_blobs.begin(); - auto tx_metadatas_iter = tx_metadatas.begin(); - while (tx_fees_iter != tx_fees.end()) { - - // init tx with outgoing transfer from filled values - shared_ptr tx = make_shared(); - txs.push_back(tx); - tx->m_hash = *tx_hashes_iter; - tx->m_key = *tx_keys_iter; - tx->m_fee = *tx_fees_iter; - tx->m_full_hex = *tx_blobs_iter; - tx->m_metadata = *tx_metadatas_iter; - shared_ptr out_transfer = make_shared(); - tx->m_outgoing_transfer = out_transfer; - out_transfer->m_amount = *tx_amounts_iter; - - // init other known fields - tx->m_is_outgoing = true; - tx->m_payment_id = config.m_payment_id; - tx->m_is_confirmed = false; - tx->m_is_miner_tx = false; - tx->m_is_failed = false; // TODO: test and handle if true - tx->m_do_not_relay = config.m_do_not_relay != boost::none && config.m_do_not_relay.get() == true; - tx->m_is_relayed = tx->m_do_not_relay.get() != true; - tx->m_in_tx_pool = !tx->m_do_not_relay.get(); - if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true - tx->m_num_confirmations = 0; - tx->m_ring_size = monero_utils::RING_SIZE; - tx->m_unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); - tx->m_is_locked = true; - if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 - out_transfer->m_account_index = config.m_account_index; - if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here - out_transfer->m_destinations = config.m_destinations; - - // iterate to next element - tx_keys_iter++; - tx_amounts_iter++; - tx_fees_iter++; - tx_hashes_iter++; - tx_blobs_iter++; - tx_metadatas_iter++; - } - - // build and return tx set - monero_tx_set tx_set; - tx_set.m_txs = txs; - if (!multisig_tx_hex.empty()) tx_set.m_multisig_tx_hex = multisig_tx_hex; - if (!unsigned_tx_hex.empty()) tx_set.m_unsigned_tx_hex = unsigned_tx_hex; - return tx_set; - } - - vector monero_wallet_core::sweep_unlocked(const monero_tx_config& config) { - - // validate config - if (config.m_destinations.size() != 1) throw runtime_error("Must specify exactly one destination to sweep to"); - if (config.m_destinations[0]->m_address == boost::none) throw runtime_error("Must specify destination address to sweep to"); - if (config.m_account_index == boost::none && config.m_subaddress_indices.size() != 0) throw runtime_error("Must specify account index if subaddress indices are specified"); - - // determine account and subaddress indices to sweep; default to all with unlocked balance if not specified - std::map> indices; - if (config.m_account_index != boost::none) { - if (config.m_subaddress_indices.size() != 0) { - indices[config.m_account_index.get()] = config.m_subaddress_indices; - } else { - vector subaddress_indices; - for (const monero_subaddress& subaddress : monero_wallet::get_subaddresses(config.m_account_index.get())) { - if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); - } - indices[config.m_account_index.get()] = subaddress_indices; - } - } else { - vector accounts = monero_wallet::get_accounts(true); - for (const monero_account& account : accounts) { - if (account.m_unlocked_balance.get() > 0) { - vector subaddress_indices; - for (const monero_subaddress& subaddress : account.m_subaddresses) { - if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); - } - indices[account.m_index.get()] = subaddress_indices; - } - } - } - - // sweep from each account and collect resulting tx sets - vector tx_sets; - for (pair> subaddress_indices_pair : indices) { - - // copy and modify the original config - monero_tx_config copy = config.copy(); - copy.m_account_index = subaddress_indices_pair.first; - copy.m_sweep_each_subaddress = false; - - // sweep all subaddresses together // TODO monero core: can this reveal outputs belong to the same wallet? - if (copy.m_sweep_each_subaddress == boost::none || copy.m_sweep_each_subaddress.get() != true) { - copy.m_subaddress_indices = subaddress_indices_pair.second; - tx_sets.push_back(sweep_account(copy)); - } - - // otherwise sweep each subaddress individually - else { - for (uint32_t subaddress_index : subaddress_indices_pair.second) { - vector subaddress_indices; - subaddress_indices.push_back(subaddress_index); - copy.m_subaddress_indices = subaddress_indices; - tx_sets.push_back(sweep_account(copy)); - } - } - } - - // return resulting tx sets - return tx_sets; - } - - monero_tx_set monero_wallet_core::sweep_account(const monero_tx_config& config) { - - // validate config - if (config.m_account_index == boost::none) throw runtime_error("Must specify account index to sweep from"); - if (config.m_destinations.size() != 1 || config.m_destinations[0]->m_address == boost::none || config.m_destinations[0]->m_address.get().empty()) throw runtime_error("Must provide exactly one destination address to sweep output to"); - if (config.m_destinations[0]->m_amount != boost::none) throw runtime_error("Cannot specify destination amount to sweep"); - if (config.m_key_image != boost::none) throw runtime_error("Cannot define key image in sweep_account(); use sweep_output() to sweep an output by its key image"); - if (config.m_sweep_each_subaddress != boost::none && config.m_sweep_each_subaddress.get() == true) throw runtime_error("Cannot sweep each subaddress individually with sweep_account"); - - // validate the transfer requested and populate dsts & extra - std::list destination; - destination.push_back(wallet_rpc::transfer_destination()); - destination.back().amount = 0; - destination.back().address = config.m_destinations[0]->m_address.get(); - string payment_id = config.m_payment_id == boost::none ? string("") : config.m_payment_id.get(); - std::vector dsts; - std::vector extra; - epee::json_rpc::error err; - if (!validate_transfer(m_w2.get(), destination, payment_id, dsts, extra, true, err)) { - throw runtime_error("Failed to validate sweep_account transfer request"); - } - - // TODO monero-core: this is default `outputs` in COMMAND_RPC_SWEEP_ALL which is not documented - uint64_t num_outputs = 1; - - // prepare parameters for wallet2's create_transactions_all() - uint64_t below_amount = config.m_below_amount == boost::none ? 0 : config.m_below_amount.get(); - uint64_t mixin = m_w2->adjust_mixin(0); - uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); - uint64_t unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); - uint32_t account_index = config.m_account_index.get(); - std::set subaddress_indices; - for (const uint32_t& subaddress_idx : config.m_subaddress_indices) subaddress_indices.insert(subaddress_idx); - - // prepare transactions - std::vector ptx_vector = m_w2->create_transactions_all(below_amount, dsts[0].addr, dsts[0].is_subaddress, num_outputs, mixin, unlock_time, priority, extra, account_index, subaddress_indices); - - // config for fill_response() - bool get_tx_keys = true; - bool get_tx_hex = true; - bool get_tx_metadata = true; - bool do_not_relay = config.m_do_not_relay != boost::none && config.m_do_not_relay.get(); - - // commit txs (if relaying) and get response using wallet rpc's fill_response() - list tx_keys; - list tx_amounts; - list tx_fees; - string multisig_tx_hex; - string unsigned_tx_hex; - list tx_hashes; - list tx_blobs; - list tx_metadatas; - if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, do_not_relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, err)) { - throw runtime_error("need to handle error filling response!"); // TODO - } - - // build sent txs from results // TODO: break this into separate utility function - vector> txs; - auto tx_hashes_iter = tx_hashes.begin(); - auto tx_keys_iter = tx_keys.begin(); - auto tx_amounts_iter = tx_amounts.begin(); - auto tx_fees_iter = tx_fees.begin(); - auto tx_blobs_iter = tx_blobs.begin(); - auto tx_metadatas_iter = tx_metadatas.begin(); - while (tx_fees_iter != tx_fees.end()) { - - // init tx with outgoing transfer from filled values - shared_ptr tx = make_shared(); - txs.push_back(tx); - tx->m_hash = *tx_hashes_iter; - tx->m_is_locked = true; - tx->m_is_outgoing = true; - tx->m_key = *tx_keys_iter; - tx->m_fee = *tx_fees_iter; - tx->m_full_hex = *tx_blobs_iter; - tx->m_metadata = *tx_metadatas_iter; - shared_ptr out_transfer = make_shared(); - tx->m_outgoing_transfer = out_transfer; - out_transfer->m_amount = *tx_amounts_iter; - shared_ptr destination = make_shared(config.m_destinations[0]->m_address.get(), out_transfer->m_amount.get()); - out_transfer->m_destinations.push_back(destination); - - // init other known fields - tx->m_payment_id = config.m_payment_id; - tx->m_is_confirmed = false; - tx->m_is_miner_tx = false; - tx->m_is_failed = false; // TODO: test and handle if true - tx->m_do_not_relay = do_not_relay; - tx->m_is_relayed = !do_not_relay; - tx->m_in_tx_pool = !do_not_relay; - if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true - tx->m_num_confirmations = 0; - tx->m_ring_size = monero_utils::RING_SIZE; - tx->m_unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); - if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 - out_transfer->m_account_index = config.m_account_index; - if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here - - // iterate to next element - tx_keys_iter++; - tx_amounts_iter++; - tx_fees_iter++; - tx_hashes_iter++; - tx_blobs_iter++; - tx_metadatas_iter++; - } - - // build and return tx set - monero_tx_set tx_set; - tx_set.m_txs = txs; - if (!multisig_tx_hex.empty()) tx_set.m_multisig_tx_hex = multisig_tx_hex; - if (!unsigned_tx_hex.empty()) tx_set.m_unsigned_tx_hex = unsigned_tx_hex; - return tx_set; - } - - monero_tx_set monero_wallet_core::sweep_output(const monero_tx_config& config) { - MTRACE("sweep_output()"); - //MTRACE("monero_tx_config: " << config.serialize()); - - // validate input config - if (config.m_key_image == boost::none || config.m_key_image.get().empty()) throw runtime_error("Must provide key image of output to sweep"); - if (config.m_destinations.size() != 1 || config.m_destinations[0]->m_address == boost::none || config.m_destinations[0]->m_address.get().empty()) throw runtime_error("Must provide exactly one destination address to sweep output to"); - - // validate the transfer queried and populate dsts & extra - string m_payment_id = config.m_payment_id == boost::none ? string("") : config.m_payment_id.get(); - std::vector dsts; - std::vector extra; - std::list destination; - destination.push_back(wallet_rpc::transfer_destination()); - destination.back().amount = 0; - destination.back().address = config.m_destinations[0]->m_address.get(); - epee::json_rpc::error err; - if (!validate_transfer(m_w2.get(), destination, m_payment_id, dsts, extra, true, err)) { - //throw runtime_error(er); // TODO - throw runtime_error("Handle validate_transfer error!"); - } - - // validate key image - crypto::key_image ki; - if (!epee::string_tools::hex_to_pod(config.m_key_image.get(), ki)) { - throw runtime_error("failed to parse key image"); - } - - // create transaction - uint64_t mixin = m_w2->adjust_mixin(0); - uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); - uint64_t unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); - std::vector ptx_vector = m_w2->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, 1, mixin, unlock_time, priority, extra); - - // validate created transaction - if (ptx_vector.empty()) throw runtime_error("No outputs found"); - if (ptx_vector.size() > 1) throw runtime_error("Multiple transactions are created, which is not supposed to happen"); - const wallet2::pending_tx &ptx = ptx_vector[0]; - if (ptx.selected_transfers.size() > 1) throw runtime_error("The transaction uses multiple inputs, which is not supposed to happen"); - - // config for fill_response() - bool get_tx_keys = true; - bool get_tx_hex = true; - bool get_tx_metadata = true; - bool m_do_not_relay = config.m_do_not_relay == boost::none ? false : config.m_do_not_relay.get(); - - // commit txs (if relaying) and get response using wallet rpc's fill_response() - list tx_keys; - list tx_amounts; - list tx_fees; - string multisig_tx_hex; - string unsigned_tx_hex; - list tx_hashes; - list tx_blobs; - list tx_metadatas; - if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, m_do_not_relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, err)) { - throw runtime_error("need to handle error filling response!"); // TODO: return err message - } - - // build sent txs from results // TODO: use common utility with send_txs() to avoid code duplication - vector> txs; - auto tx_hashes_iter = tx_hashes.begin(); - auto tx_keys_iter = tx_keys.begin(); - auto tx_amounts_iter = tx_amounts.begin(); - auto tx_fees_iter = tx_fees.begin(); - auto tx_blobs_iter = tx_blobs.begin(); - auto tx_metadatas_iter = tx_metadatas.begin(); - while (tx_fees_iter != tx_fees.end()) { - - // init tx with outgoing transfer from filled values - shared_ptr tx = make_shared(); - txs.push_back(tx); - tx->m_hash = *tx_hashes_iter; - tx->m_is_outgoing = true; - tx->m_key = *tx_keys_iter; - tx->m_fee = *tx_fees_iter; - tx->m_full_hex = *tx_blobs_iter; - tx->m_metadata = *tx_metadatas_iter; - shared_ptr out_transfer = make_shared(); - tx->m_outgoing_transfer = out_transfer; - out_transfer->m_amount = *tx_amounts_iter; - - // init other known fields - tx->m_payment_id = config.m_payment_id; - tx->m_is_confirmed = false; - tx->m_is_miner_tx = false; - tx->m_is_failed = false; // TODO: test and handle if true - tx->m_do_not_relay = config.m_do_not_relay != boost::none && config.m_do_not_relay.get() == true; - tx->m_is_relayed = tx->m_do_not_relay.get() != true; - tx->m_in_tx_pool = !tx->m_do_not_relay.get(); - if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true - tx->m_num_confirmations = 0; - tx->m_ring_size = monero_utils::RING_SIZE; - tx->m_unlock_time = config.m_unlock_time == boost::none ? 0 : config.m_unlock_time.get(); - tx->m_is_locked = true; - if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 - out_transfer->m_account_index = config.m_account_index; - if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here - out_transfer->m_destinations = config.m_destinations; - out_transfer->m_destinations[0]->m_amount = *tx_amounts_iter; - - // iterate to next element - tx_keys_iter++; - tx_amounts_iter++; - tx_fees_iter++; - tx_hashes_iter++; - tx_blobs_iter++; - tx_metadatas_iter++; - } - - // validate one transaction - if (txs.size() != 1) throw runtime_error("Expected 1 transaction but was " + boost::lexical_cast(txs.size())); - - // build and return tx set - monero_tx_set tx_set; - tx_set.m_txs = txs; - if (!multisig_tx_hex.empty()) tx_set.m_multisig_tx_hex = multisig_tx_hex; - if (!unsigned_tx_hex.empty()) tx_set.m_unsigned_tx_hex = unsigned_tx_hex; - return tx_set; - } - - monero_tx_set monero_wallet_core::sweep_dust(bool do_not_relay) { - MTRACE("monero_wallet_core::sweep_dust()"); - - // create transaction to fill - std::vector ptx_vector = m_w2->create_unmixable_sweep_transactions(); - - // config for fill_response - bool get_tx_keys = true; - bool get_tx_hex = true; - bool get_tx_metadata = true; - - // commit txs (if relaying) and get response using wallet rpc's fill_response() - list tx_keys; - list tx_amounts; - list tx_fees; - string multisig_tx_hex; - string unsigned_tx_hex; - list tx_hashes; - list tx_blobs; - list tx_metadatas; - epee::json_rpc::error er; - if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_fees, multisig_tx_hex, unsigned_tx_hex, do_not_relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, er)) { - throw runtime_error("need to handle error filling response!"); // TODO: return err message - } - - // build sent txs from results // TODO: use common utility with send_txs() to avoid code duplication - vector> txs; - auto tx_hashes_iter = tx_hashes.begin(); - auto tx_keys_iter = tx_keys.begin(); - auto tx_amounts_iter = tx_amounts.begin(); - auto tx_fees_iter = tx_fees.begin(); - auto tx_blobs_iter = tx_blobs.begin(); - auto tx_metadatas_iter = tx_metadatas.begin(); - while (tx_fees_iter != tx_fees.end()) { - - // init tx with outgoing transfer from filled values - shared_ptr tx = make_shared(); - txs.push_back(tx); - tx->m_hash = *tx_hashes_iter; - tx->m_is_outgoing = true; - tx->m_key = *tx_keys_iter; - tx->m_fee = *tx_fees_iter; - tx->m_full_hex = *tx_blobs_iter; - tx->m_metadata = *tx_metadatas_iter; - shared_ptr out_transfer = make_shared(); - tx->m_outgoing_transfer = out_transfer; - out_transfer->m_amount = *tx_amounts_iter; - - // init other known fields - tx->m_is_confirmed = false; - tx->m_is_miner_tx = false; - tx->m_is_failed = false; // TODO: test and handle if true - tx->m_do_not_relay = do_not_relay; - tx->m_is_relayed = tx->m_do_not_relay.get() != true; - tx->m_in_tx_pool = !tx->m_do_not_relay.get(); - if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true - tx->m_num_confirmations = 0; - tx->m_ring_size = monero_utils::RING_SIZE; - tx->m_unlock_time = 0; - if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero core: this should be encapsulated in wallet2 - out_transfer->m_destinations[0]->m_amount = *tx_amounts_iter; - - // iterate to next element - tx_keys_iter++; - tx_amounts_iter++; - tx_fees_iter++; - tx_hashes_iter++; - tx_blobs_iter++; - tx_metadatas_iter++; - } - - // throw exception if no dust to sweep (dust only exists pre-rct) - if (txs.empty() && multisig_tx_hex.empty() && unsigned_tx_hex.empty()) throw runtime_error("No dust to sweep"); - - // build and return tx set - monero_tx_set tx_set; - tx_set.m_txs = txs; - if (!multisig_tx_hex.empty()) tx_set.m_multisig_tx_hex = multisig_tx_hex; - if (!unsigned_tx_hex.empty()) tx_set.m_unsigned_tx_hex = unsigned_tx_hex; - return tx_set; - } - monero_tx_set monero_wallet_core::parse_tx_set(const monero_tx_set& tx_set) { // get unsigned and multisig tx sets @@ -2913,14 +2853,15 @@ namespace monero { MTRACE("create_payment_uri()"); // validate config - if (config.m_destinations.size() != 1) throw runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds"); - if (config.m_destinations.at(0)->m_address == boost::none) throw runtime_error("Cannot make URI from supplied parameters: must provide destination address"); - if (config.m_destinations.at(0)->m_amount == boost::none) throw runtime_error("Cannot make URI from supplied parameters: must provide destination amount"); + vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds"); + if (destinations.at(0)->m_address == boost::none) throw runtime_error("Cannot make URI from supplied parameters: must provide destination address"); + if (destinations.at(0)->m_amount == boost::none) throw runtime_error("Cannot make URI from supplied parameters: must provide destination amount"); // prepare wallet2 params - string address = config.m_destinations.at(0)->m_address.get(); + string address = destinations.at(0)->m_address.get(); string payment_id = config.m_payment_id == boost::none ? "" : config.m_payment_id.get(); - uint64_t amount = config.m_destinations.at(0)->m_amount.get(); + uint64_t amount = destinations.at(0)->m_amount.get(); string note = config.m_note == boost::none ? "" : config.m_note.get(); string m_recipient_name = config.m_recipient_name == boost::none ? "" : config.m_recipient_name.get(); diff --git a/src/wallet/monero_wallet_core.h b/src/wallet/monero_wallet_core.h index 066cf95..e896cff 100644 --- a/src/wallet/monero_wallet_core.h +++ b/src/wallet/monero_wallet_core.h @@ -172,119 +172,109 @@ namespace monero { /** * Supported wallet methods. */ - bool is_watch_only() const { return m_w2->watch_only(); } - void set_daemon_connection(const string& uri, const string& username = "", const string& password = ""); - void set_daemon_connection(const boost::optional& connection); - boost::optional get_daemon_connection() const; - bool is_connected() const; - bool is_daemon_synced() const; - bool is_daemon_trusted() const; - bool is_synced() const; - monero_version get_version() const; - string get_path() const; - monero_network_type get_network_type() const; - string get_mnemonic() const; - string get_mnemonic_language() const; - string get_public_view_key() const; - string get_private_view_key() const; - string get_public_spend_key() const; - string get_private_spend_key() const; - string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const; - monero_subaddress get_address_index(const string& address) const; - monero_integrated_address get_integrated_address(const string& standard_address = "", const string& payment_id = "") const; - monero_integrated_address decode_integrated_address(const string& integrated_address) const; - uint64_t get_height() const; - uint64_t get_sync_height() const; - void set_sync_height(uint64_t sync_height); - uint64_t get_daemon_height() const; - uint64_t get_daemon_max_peer_height() const; - void add_listener(monero_wallet_listener& listener); - void remove_listener(monero_wallet_listener& listener); - set get_listeners(); - monero_sync_result sync(); - monero_sync_result sync(monero_wallet_listener& listener); - monero_sync_result sync(uint64_t start_height); - monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener); - void start_syncing(); - void stop_syncing(); - void rescan_spent(); - void rescan_blockchain(); - uint64_t get_balance() const; - uint64_t get_balance(uint32_t account_idx) const; - uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const; - uint64_t get_unlocked_balance() const; - uint64_t get_unlocked_balance(uint32_t account_idx) const; - uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const; - vector get_accounts(bool include_subaddresses, const string& tag) const; - monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const; - monero_account create_account(const string& label = ""); - vector get_subaddresses(const uint32_t account_idx, const vector& subaddress_indices) const; - monero_subaddress create_subaddress(uint32_t account_idx, const string& label = ""); - vector> get_txs() const; - vector> get_txs(const monero_tx_query& query) const; - vector> get_txs(const monero_tx_query& query, vector& missing_tx_hashes) const; - vector> get_transfers(const monero_transfer_query& query) const; - vector> get_outputs(const monero_output_query& query) const; - string get_outputs_hex() const; - int import_outputs_hex(const string& outputs_hex); - vector> get_key_images() const; - shared_ptr import_key_images(const vector>& key_images); - monero_tx_set create_tx(monero_tx_config& config); - monero_tx_set create_tx(uint32_t account_index, string address, uint64_t amount); - monero_tx_set create_tx(int account_index, string address, uint64_t amount, monero_tx_priority priority); - monero_tx_set create_txs(monero_tx_config& config); - string relay_tx(const string& tx_metadata); - string relay_tx(const monero_tx_wallet& tx); - vector relay_txs(const vector& tx_metadatas); - vector relay_txs(const vector>& txs); - monero_tx_set send_tx(const monero_tx_config& config); - monero_tx_set send_tx(uint32_t account_index, string address, uint64_t amount); - monero_tx_set send_tx(uint32_t account_index, string address, uint64_t amount, monero_tx_priority priority); - monero_tx_set send_txs(const monero_tx_config& config); - vector sweep_unlocked(const monero_tx_config& config); - monero_tx_set sweep_output(const monero_tx_config& config); - monero_tx_set sweep_dust(bool do_not_relay = false); - monero_tx_set parse_tx_set(const monero_tx_set& tx_set); - string sign_txs(const string& unsigned_tx_hex); - vector submit_txs(const string& signed_tx_hex); - string sign_message(const string& msg) const; - bool verify_message(const string& msg, const string& address, const string& signature) const; - string get_tx_key(const string& tx_hash) const; - shared_ptr check_tx_key(const string& tx_hash, const string& txKey, const string& address) const; - string get_tx_proof(const string& tx_hash, const string& address, const string& message) const; - shared_ptr check_tx_proof(const string& tx_hash, const string& address, const string& message, const string& signature) const; - string get_spend_proof(const string& tx_hash, const string& message) const; - bool check_spend_proof(const string& tx_hash, const string& message, const string& signature) const; - string get_reserve_proof_wallet(const string& message) const; - string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const string& message) const; - shared_ptr check_reserve_proof(const string& address, const string& message, const string& signature) const; - string get_tx_note(const string& tx_hash) const; - vector get_tx_notes(const vector& tx_hashes) const; - void set_tx_note(const string& tx_hash, const string& note); - void set_tx_notes(const vector& tx_hashes, const vector& notes); - vector get_address_book_entries(const vector& indices) const; - uint64_t add_address_book_entry(const string& address, const string& description); - void edit_address_book_entry(uint64_t index, bool set_address, const string& address, bool set_description, const string& description); - void delete_address_book_entry(uint64_t index); - string create_payment_uri(const monero_tx_config& config) const; - shared_ptr parse_payment_uri(const string& uri) const; - bool get_attribute(const string& key, string& value) const; - void set_attribute(const string& key, const string& val); - void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery); - void stop_mining(); - uint64_t wait_for_next_block(); - bool is_multisig_import_needed() const; - monero_multisig_info get_multisig_info(); - string prepare_multisig(); - monero_multisig_init_result make_multisig(const vector& multisig_hexes, int threshold, const string& password); - monero_multisig_init_result exchange_multisig_keys(const vector& mutisig_hexes, const string& password); - string get_multisig_hex(); - int import_multisig_hex(const vector& multisig_hexes); - monero_multisig_sign_result sign_multisig_tx_hex(const string& multisig_tx_hex); - vector submit_multisig_tx_hex(const string& signed_multisig_tx_hex); - void save(); - void move_to(string path, string password); - void close(bool save = false); + bool is_watch_only() const override { return m_w2->watch_only(); } + void set_daemon_connection(const string& uri, const string& username = "", const string& password = "") override; + void set_daemon_connection(const boost::optional& connection) override; + boost::optional get_daemon_connection() const override; + bool is_connected() const override; + bool is_daemon_synced() const override; + bool is_daemon_trusted() const override; + bool is_synced() const override; + monero_version get_version() const override; + string get_path() const override; + monero_network_type get_network_type() const override; + string get_mnemonic() const override; + string get_mnemonic_language() const override; + string get_public_view_key() const override; + string get_private_view_key() const override; + string get_public_spend_key() const override; + string get_private_spend_key() const override; + string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_subaddress get_address_index(const string& address) const override; + monero_integrated_address get_integrated_address(const string& standard_address = "", const string& payment_id = "") const override; + monero_integrated_address decode_integrated_address(const string& integrated_address) const override; + uint64_t get_height() const override; + uint64_t get_sync_height() const override; + void set_sync_height(uint64_t sync_height) override; + uint64_t get_daemon_height() const override; + uint64_t get_daemon_max_peer_height() const override; + void add_listener(monero_wallet_listener& listener) override; + void remove_listener(monero_wallet_listener& listener) override; + set get_listeners() override; + monero_sync_result sync() override; + monero_sync_result sync(monero_wallet_listener& listener) override; + monero_sync_result sync(uint64_t start_height) override; + monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener) override; + void start_syncing() override; + void stop_syncing() override; + void rescan_spent() override; + void rescan_blockchain() override; + uint64_t get_balance() const override; + uint64_t get_balance(uint32_t account_idx) const override; + uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + uint64_t get_unlocked_balance() const override; + uint64_t get_unlocked_balance(uint32_t account_idx) const override; + uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + vector get_accounts(bool include_subaddresses, const string& tag) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + monero_account create_account(const string& label = "") override; + vector get_subaddresses(const uint32_t account_idx, const vector& subaddress_indices) const override; + monero_subaddress create_subaddress(uint32_t account_idx, const string& label = "") override; + vector> get_txs() const override; + vector> get_txs(const monero_tx_query& query) const override; + vector> get_txs(const monero_tx_query& query, vector& missing_tx_hashes) const override; + vector> get_transfers(const monero_transfer_query& query) const override; + vector> get_outputs(const monero_output_query& query) const override; + string get_outputs_hex() const override; + int import_outputs_hex(const string& outputs_hex) override; + vector> get_key_images() const override; + shared_ptr import_key_images(const vector>& key_images) override; + vector> create_txs(const monero_tx_config& config) override; + vector> sweep_unlocked(const monero_tx_config& config) override; + shared_ptr sweep_output(const monero_tx_config& config) override; + vector> sweep_dust(bool relay = false) override; + vector relay_txs(const vector& tx_metadatas) override; + monero_tx_set parse_tx_set(const monero_tx_set& tx_set) override; + string sign_txs(const string& unsigned_tx_hex) override; + vector submit_txs(const string& signed_tx_hex) override; + string sign_message(const string& msg) const override; + bool verify_message(const string& msg, const string& address, const string& signature) const override; + string get_tx_key(const string& tx_hash) const override; + shared_ptr check_tx_key(const string& tx_hash, const string& txKey, const string& address) const override; + string get_tx_proof(const string& tx_hash, const string& address, const string& message) const override; + shared_ptr check_tx_proof(const string& tx_hash, const string& address, const string& message, const string& signature) const override; + string get_spend_proof(const string& tx_hash, const string& message) const override; + bool check_spend_proof(const string& tx_hash, const string& message, const string& signature) const override; + string get_reserve_proof_wallet(const string& message) const override; + string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const string& message) const override; + shared_ptr check_reserve_proof(const string& address, const string& message, const string& signature) const override; + string get_tx_note(const string& tx_hash) const override; + vector get_tx_notes(const vector& tx_hashes) const override; + void set_tx_note(const string& tx_hash, const string& note) override; + void set_tx_notes(const vector& tx_hashes, const vector& notes) override; + vector get_address_book_entries(const vector& indices) const override; + uint64_t add_address_book_entry(const string& address, const string& description) override; + void edit_address_book_entry(uint64_t index, bool set_address, const string& address, bool set_description, const string& description) override; + void delete_address_book_entry(uint64_t index) override; + string create_payment_uri(const monero_tx_config& config) const override; + shared_ptr parse_payment_uri(const string& uri) const override; + bool get_attribute(const string& key, string& value) const override; + void set_attribute(const string& key, const string& val) override; + void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) override; + void stop_mining() override; + uint64_t wait_for_next_block() override; + bool is_multisig_import_needed() const override; + monero_multisig_info get_multisig_info() override; + string prepare_multisig() override; + monero_multisig_init_result make_multisig(const vector& multisig_hexes, int threshold, const string& password) override; + monero_multisig_init_result exchange_multisig_keys(const vector& mutisig_hexes, const string& password) override; + string get_multisig_hex() override; + int import_multisig_hex(const vector& multisig_hexes) override; + monero_multisig_sign_result sign_multisig_tx_hex(const string& multisig_tx_hex) override; + vector submit_multisig_tx_hex(const string& signed_multisig_tx_hex) override; + void save() override; + void move_to(string path, string password) override; + void close(bool save = false) override; /** * Wallet import and export using buffers and not the file system. @@ -302,7 +292,7 @@ namespace monero { void init_common(); vector get_subaddresses_aux(uint32_t account_idx, const vector& subaddress_indices, const vector& transfers) const; - monero_tx_set sweep_account(const monero_tx_config& config); // sweeps unlocked funds within an account; private helper to sweep_unlocked() + vector> sweep_account(const monero_tx_config& config); // sweeps unlocked funds within an account; private helper to sweep_unlocked() // blockchain sync management mutable std::atomic m_is_synced; // whether or not wallet is synced diff --git a/src/wallet/monero_wallet_keys.h b/src/wallet/monero_wallet_keys.h index 383c630..0f95f72 100644 --- a/src/wallet/monero_wallet_keys.h +++ b/src/wallet/monero_wallet_keys.h @@ -116,24 +116,24 @@ namespace monero { /** * Supported wallet methods. */ - bool is_watch_only() const { return m_is_watch_only; } - monero_version get_version() const; - monero_network_type get_network_type() const { return m_network_type; } - string get_mnemonic() const { return m_mnemonic; } - string get_mnemonic_language() const { return m_language; } - string get_private_view_key() const { return m_prv_view_key; } - string get_private_spend_key() const { return m_prv_spend_key; } - string get_public_view_key() const { return m_pub_view_key; } - string get_public_spend_key() const { return m_pub_spend_key; } - string get_primary_address() const { return m_primary_address; } - string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const; - monero_integrated_address get_integrated_address(const string& standard_address = "", const string& payment_id = "") const; - monero_integrated_address decode_integrated_address(const string& integrated_address) const; - monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const; - vector get_subaddresses(const uint32_t account_idx, const vector& subaddress_indices) const; - string sign_message(const string& msg) const; - bool verify_message(const string& msg, const string& address, const string& signature) const; - void close(bool save = false); + bool is_watch_only() const override { return m_is_watch_only; } + monero_version get_version() const override; + monero_network_type get_network_type() const override { return m_network_type; } + string get_mnemonic() const override { return m_mnemonic; } + string get_mnemonic_language() const override { return m_language; } + string get_private_view_key() const override { return m_prv_view_key; } + string get_private_spend_key() const override { return m_prv_spend_key; } + string get_public_view_key() const override { return m_pub_view_key; } + string get_public_spend_key() const override { return m_pub_spend_key; } + string get_primary_address() const override { return m_primary_address; } + string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_integrated_address get_integrated_address(const string& standard_address = "", const string& payment_id = "") const override; + monero_integrated_address decode_integrated_address(const string& integrated_address) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + vector get_subaddresses(const uint32_t account_idx, const vector& subaddress_indices) const override; + string sign_message(const string& msg) const override; + bool verify_message(const string& msg, const string& address, const string& signature) const override; + void close(bool save = false) override; // --------------------------------- PRIVATE -------------------------------- diff --git a/src/wallet/monero_wallet_model.cpp b/src/wallet/monero_wallet_model.cpp index ebaf3e8..ef723b4 100644 --- a/src/wallet/monero_wallet_model.cpp +++ b/src/wallet/monero_wallet_model.cpp @@ -399,7 +399,7 @@ namespace monero { if (m_payment_id != boost::none && m_payment_id != tx->m_payment_id) return false; if (m_is_confirmed != boost::none && m_is_confirmed != tx->m_is_confirmed) return false; if (m_in_tx_pool != boost::none && m_in_tx_pool != tx->m_in_tx_pool) return false; - if (m_do_not_relay != boost::none && m_do_not_relay != tx->m_do_not_relay) return false; + if (m_relay != boost::none && m_relay != tx->m_relay) return false; if (m_is_failed != boost::none && m_is_failed != tx->m_is_failed) return false; if (m_is_miner_tx != boost::none && m_is_miner_tx != tx->m_is_miner_tx) return false; if (m_is_locked != boost::none && m_is_locked != tx->m_is_locked) return false; @@ -1039,9 +1039,11 @@ namespace monero { return true; } - // ------------------------- MONERO SEND REQUEST ---------------------------- + // --------------------------- MONERO TX CONFIG ----------------------------- monero_tx_config::monero_tx_config(const monero_tx_config& config) { + m_address = config.m_address; + m_amount = config.m_amount; if (config.m_destinations.size() > 0) { for (const shared_ptr& destination : config.m_destinations) { m_destinations.push_back(destination->copy(destination, make_shared())); @@ -1055,7 +1057,7 @@ namespace monero { m_subaddress_indices = config.m_subaddress_indices; m_unlock_time = config.m_unlock_time; m_can_split = config.m_can_split; - m_do_not_relay = config.m_do_not_relay; + m_relay = config.m_relay; m_note = config.m_note; m_recipient_name = config.m_recipient_name; m_below_amount = config.m_below_amount; @@ -1063,6 +1065,10 @@ namespace monero { m_key_image = config.m_key_image; } + monero_tx_config monero_tx_config::copy() const { + return monero_tx_config(*this); + } + rapidjson::Value monero_tx_config::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { // create root @@ -1085,7 +1091,7 @@ namespace monero { // set bool values if (m_can_split != boost::none) monero_utils::addJsonMember("canSplit", m_can_split.get(), allocator, root); - if (m_do_not_relay != boost::none) monero_utils::addJsonMember("doNotRelay", m_do_not_relay.get(), allocator, root); + if (m_relay != boost::none) monero_utils::addJsonMember("relay", m_relay.get(), allocator, root); if (m_sweep_each_subaddress != boost::none) monero_utils::addJsonMember("sweepEachSubaddress", m_sweep_each_subaddress.get(), allocator, root); // set sub-arrays @@ -1130,7 +1136,7 @@ namespace monero { else if (key == string("subaddressIndices")) for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) config->m_subaddress_indices.push_back(it2->second.get_value()); else if (key == string("unlockTime")) config->m_unlock_time = it->second.get_value(); else if (key == string("canSplit")) config->m_can_split = it->second.get_value(); - else if (key == string("doNotRelay")) config->m_do_not_relay = it->second.get_value(); + else if (key == string("relay")) config->m_relay = it->second.get_value(); else if (key == string("note")) config->m_note = it->second.data(); else if (key == string("recipientName")) config->m_recipient_name = it->second.data(); else if (key == string("belowAmount")) config->m_below_amount = it->second.get_value(); @@ -1141,8 +1147,18 @@ namespace monero { return config; } - monero_tx_config monero_tx_config::copy() const { - return monero_tx_config(*this); + vector> monero_tx_config::get_normalized_destinations() const { + if (m_address == boost::none && m_amount == boost::none) return m_destinations; + else if (m_destinations.empty()) { + vector> destinations; + destinations.push_back(std::make_shared(m_address, m_amount)); + return destinations; + } else { + if (m_destinations.size() > 1) throw runtime_error("Invalid tx configuration: single destination address/amount incompatible with multiple destinations"); + if (m_address != m_destinations[0]->m_address) throw runtime_error("Invalid tx configuration: single destination address does not match first destination address"); + if (m_amount != m_destinations[0]->m_amount) throw runtime_error("Invalid tx configuration: single destination amount does not match first destination amount"); + return m_destinations; + } } // ---------------------- MONERO INTEGRATED ADDRESS ------------------------- diff --git a/src/wallet/monero_wallet_model.h b/src/wallet/monero_wallet_model.h index 035759a..08d253b 100644 --- a/src/wallet/monero_wallet_model.h +++ b/src/wallet/monero_wallet_model.h @@ -112,8 +112,7 @@ namespace monero { boost::optional m_address; boost::optional m_amount; - monero_destination() {} - monero_destination(const string& address, uint64_t amount) : m_address(address), m_amount(amount) {} + monero_destination(boost::optional address = boost::none, boost::optional amount = boost::none) : m_address(address), m_amount(amount) {} rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; static void from_property_tree(const boost::property_tree::ptree& node, const shared_ptr& destination); shared_ptr copy(const shared_ptr& src, const shared_ptr& tgt) const; @@ -339,6 +338,8 @@ namespace monero { * Configures a transaction to send, sweep, or create a payment URI. */ struct monero_tx_config : public serializable_struct { + boost::optional m_address; + boost::optional m_amount; vector> m_destinations; boost::optional m_payment_id; boost::optional m_priority; @@ -348,7 +349,7 @@ namespace monero { vector m_subaddress_indices; boost::optional m_unlock_time; boost::optional m_can_split; - boost::optional m_do_not_relay; + boost::optional m_relay; boost::optional m_note; boost::optional m_recipient_name; boost::optional m_below_amount; @@ -357,9 +358,10 @@ namespace monero { monero_tx_config() {} monero_tx_config(const monero_tx_config& config); + monero_tx_config copy() const; rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; static shared_ptr deserialize(const string& config_json); - monero_tx_config copy() const; + vector> get_normalized_destinations() const; }; /** diff --git a/test/sample_code.cpp b/test/sample_code.cpp index 879c120..2accc41 100644 --- a/test/sample_code.cpp +++ b/test/sample_code.cpp @@ -22,13 +22,16 @@ int main(int argc, const char* argv[]) { // create a wallet from a mnemonic phrase string mnemonic = "hefty value later extra artistic firm radar yodel talent future fungal nutshell because sanity awesome nail unjustly rage unafraid cedar delayed thumbs comb custom sanity"; monero_wallet* wallet_restored = monero_wallet_core::create_wallet_from_mnemonic( - "MyWalletRestored", // wallet path and name - "supersecretpassword123", // wallet password - monero_network_type::STAGENET, // network type - mnemonic, // mnemonic phrase - monero_rpc_connection(string("http://localhost:38081")), // daemon connection - 380104, // restore height - "" // seed offset + "MyWalletRestored", // wallet path and name + "supersecretpassword123", // wallet password + monero_network_type::STAGENET, // network type + mnemonic, // mnemonic phrase + monero_rpc_connection( // daemon connection + string("http://localhost:38081"), + string("superuser"), + string("abctesting123")), + 380104, // restore height + "" // seed offset ); // synchronize the wallet and receive progress notifications @@ -70,12 +73,14 @@ int main(int argc, const char* argv[]) { // create and sync a new wallet with a random mnemonic phrase monero_wallet* wallet_random = monero_wallet_core::create_wallet_random( - "MyWalletRandom", // wallet path and name - "supersecretpassword123", // wallet password - monero_network_type::STAGENET, // network type - monero_rpc_connection(string("http://localhost:38081")), // daemon connection - "English" - ); + "MyWalletRandom", // wallet path and name + "supersecretpassword123", // wallet password + monero_network_type::STAGENET, // network type + monero_rpc_connection( // daemon connection + string("http://localhost:38081"), + string("superuser"), + string("abctesting123")), + "English"); wallet_random->sync(); // continuously synchronize the wallet in the background @@ -100,7 +105,12 @@ int main(int argc, const char* argv[]) { wallet_random->add_listener(my_listener); // send funds from the restored wallet to the random wallet - shared_ptr sent_tx = wallet_restored->send_tx(0, wallet_random->get_address(1, 0), 50000).m_txs[0]; + monero_tx_config config; + config.m_account_index = 0; + config.m_address = wallet_random->get_address(1, 0); + config.m_amount = 50000; + config.m_relay = true; + shared_ptr sent_tx = wallet_restored->create_tx(config); bool in_pool = sent_tx->m_in_tx_pool.get(); // true // mine with 7 threads to push the network along @@ -115,20 +125,21 @@ int main(int argc, const char* argv[]) { // stop mining wallet_restored->stop_mining(); - // create a request to send funds to multiple destinations in the random wallet - monero_tx_config config = monero_tx_config(); + // create config to send funds to multiple destinations in the random wallet + config = monero_tx_config(); config.m_account_index = 1; // withdraw funds from this account config.m_subaddress_indices = vector(); config.m_subaddress_indices.push_back(0); config.m_subaddress_indices.push_back(1); // withdraw funds from these subaddresses within the account config.m_priority = monero_tx_priority::UNIMPORTANT; // no rush + config.m_relay = true; vector> destinations; // specify the recipients and their amounts destinations.push_back(make_shared(wallet_random->get_address(1, 0), 50000)); destinations.push_back(make_shared(wallet_random->get_address(2, 0), 50000)); config.m_destinations = destinations; // create the transaction, confirm with the user, and relay to the network - shared_ptr created_tx = wallet_restored->create_tx(config).m_txs[0]; + shared_ptr created_tx = wallet_restored->create_tx(config); uint64_t fee = created_tx->m_fee.get(); // "Are you sure you want to send ...?" wallet_restored->relay_tx(*created_tx); // submit the transaction to the Monero network which will notify the recipient wallet