diff --git a/src/ga_tx.cpp b/src/ga_tx.cpp index b62f5c839..093f3d81a 100644 --- a/src/ga_tx.cpp +++ b/src/ga_tx.cpp @@ -749,11 +749,7 @@ namespace sdk { // so compute what we can send (everything minus the // fee) and exit the loop required_total = available_total - fee; - if (is_liquid) { - set_tx_output_commitment(net_params, tx, 0, asset_tag, required_total.value()); - } else { - tx->outputs[0].satoshi = required_total.value(); - } + set_tx_output_value(net_params, tx, 0, asset_tag, required_total.value()); if (num_addressees == 1u) { addressees_p->at(0)["satoshi"] = required_total.value(); } @@ -809,9 +805,41 @@ namespace sdk { continue; } - // We have more than the dust amount of change. Add a change - // output to collect it, then loop again in case the amount - // this increases the fee by requires more UTXOs. + // We have more than the dust amount of change. First look for an explicit change + // output in the addressees and if present send the change there + amount::value_type change_amount = (total - required_total - fee).value(); + bool have_explicit_change = false; + if (num_addressees) { + int addressee_index = 0; + for (auto& addressee : *addressees_p) { + const auto addressee_asset_tag + = session.asset_id_from_string(addressee.value("asset_tag", std::string{})); + if (addressee_asset_tag == asset_tag) { + if (json_get_value(addressee, "is_change", false)) { + if (have_explicit_change) { + set_tx_error(result, "Only one explicit change addressee allowed"); + break; + } + // There is an assumption that the tx outputs are indexed so they match + // addressees, which is very weakly enforced but needs to be true otherwise + // other things (liquid blinding) breaks. + set_tx_output_value(net_params, tx, addressee_index, asset_tag, change_amount); + addressee["satoshi"] = change_amount; + have_explicit_change = true; + required_total += change_amount; + } + } + ++addressee_index; + } + } + + if (have_explicit_change) { + // No need for implicit change output + continue; + } + + // No explicit change output specified, generate a change output paying back to + // the wallet. add_tx_output(net_params, result, tx, result.at("change_address").at(asset_tag).at("address"), is_liquid ? 1 : 0, asset_tag == "btc" ? std::string{} : asset_tag); have_change_output = true; diff --git a/src/transaction_utils.cpp b/src/transaction_utils.cpp index cba02a67f..d678a1ac6 100644 --- a/src/transaction_utils.cpp +++ b/src/transaction_utils.cpp @@ -355,8 +355,10 @@ namespace sdk { } // Transactions with outputs below the dust threshold (except OP_RETURN) - // are not relayed by network nodes - if (!result.value("send_all", false) && satoshi.value() < session.get_dust_threshold()) { + // are not relayed by network nodes. send_all and explicit change outputs + // have amounts set to zero because they are calculated + if (!result.value("send_all", false) && !json_get_value(addressee, "is_change", false) + && satoshi.value() < session.get_dust_threshold()) { result["error"] = res::id_invalid_amount; } @@ -367,6 +369,17 @@ namespace sdk { net_params, result, tx, address, satoshi.value(), addressee.value("asset_tag", std::string{})); } + void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index, + const std::string& asset_tag, amount::value_type satoshi) + { + const bool is_liquid = net_params.liquid(); + if (is_liquid) { + set_tx_output_commitment(net_params, tx, index, asset_tag, satoshi); + } else { + tx->outputs[index].satoshi = satoshi; + } + } + void update_tx_size_info(const wally_tx_ptr& tx, nlohmann::json& result) { const bool valid = tx->num_inputs != 0u && tx->num_outputs != 0u; @@ -473,6 +486,14 @@ namespace sdk { if (net_params.liquid()) { output["public_key"] = blinding_key_from_addr(address); } + const bool is_change = json_get_value(addressee, "is_change", false); + if (is_change) { + // This is an explicit change output (as opposed to one generated by the gdk) + // Mark with 'is_change' + // Can be distinguished from a generated change output because the change_index + // will be set to -1 + output["is_change"] = true; + } ++addressee_index; } diff --git a/src/transaction_utils.hpp b/src/transaction_utils.hpp index 790e7698c..6dc9f3c6f 100644 --- a/src/transaction_utils.hpp +++ b/src/transaction_utils.hpp @@ -84,6 +84,9 @@ namespace sdk { amount add_tx_addressee(ga_session& session, const network_parameters& net_params, nlohmann::json& result, wally_tx_ptr& tx, nlohmann::json& addressee); + void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index, + const std::string& asset_tag, amount::value_type satoshi); + vbf_t generate_final_vbf(byte_span_t input_abfs, byte_span_t input_vbfs, uint64_span_t input_values, const std::vector& output_abfs, const std::vector& output_vbfs, uint32_t num_inputs);