Skip to content

Commit 1d97818

Browse files
committed
Support explicit change outputs
Setting the `is_change` flag on an addressee when creating a transaction routes all change to that output. The default behaviour is to generate a change address for the wallet. Only one explicit change address per asset is allowed. If an explicit change address is specified for a given asset it follows that there will not be a generated change address for that asset. The tx output in the transaction json corresponding to the explicit change output will be flagged with `is_change`, just like a generated change address output, however the `change_index` will be set to -1. IOW `change_index` should be interpreted as the index of the generated (implied) change output, if there is one.
1 parent 5cf1614 commit 1d97818

File tree

3 files changed

+73
-25
lines changed

3 files changed

+73
-25
lines changed

src/ga_tx.cpp

+45-21
Original file line numberDiff line numberDiff line change
@@ -614,15 +614,28 @@ namespace sdk {
614614
// Add all outputs and compute the total amount of satoshi to be sent
615615
amount required_total{ 0 };
616616

617+
uint32_t explicit_change_index = NO_CHANGE_INDEX;
617618
if (num_addressees) {
619+
size_t addressee_index = 0;
618620
for (auto& addressee : *addressees_p) {
619621
const auto addressee_asset_id = asset_id_from_json(net_params, addressee);
620622
if (addressee_asset_id == asset_id) {
621-
required_total += add_tx_addressee(session, net_params, result, tx, addressee);
623+
const auto amount = add_tx_addressee(session, net_params, result, tx, addressee);
624+
if (!json_get_value(addressee, "is_change", false)) {
625+
required_total += amount;
626+
} else {
627+
if (explicit_change_index != NO_CHANGE_INDEX) {
628+
set_tx_error(result, "Only one explicit change addressee allowed");
629+
break;
630+
}
631+
explicit_change_index = addressee_index;
632+
}
622633
reordered_addressees.push_back(addressee);
623634
}
635+
++addressee_index;
624636
}
625637
}
638+
result["change_type"][asset_id] = explicit_change_index == NO_CHANGE_INDEX ? "generated" : "explicit";
626639

627640
// TODO: filter per asset or assume always single asset
628641
if (manual_selection) {
@@ -758,11 +771,7 @@ namespace sdk {
758771
// so compute what we can send (everything minus the
759772
// fee) and exit the loop
760773
required_total = available_total - fee;
761-
if (is_liquid) {
762-
set_tx_output_commitment(net_params, tx, 0, asset_id, required_total.value());
763-
} else {
764-
tx->outputs[0].satoshi = required_total.value();
765-
}
774+
set_tx_output_value(net_params, tx, 0, asset_id, required_total.value());
766775
if (num_addressees == 1u) {
767776
addressees_p->at(0)["satoshi"] = required_total.value();
768777
}
@@ -818,17 +827,30 @@ namespace sdk {
818827
continue;
819828
}
820829

821-
// We have more than the dust amount of change. Add a change
822-
// output to collect it, then loop again in case the amount
823-
// this increases the fee by requires more UTXOs.
824-
add_tx_output(net_params, result, tx, result.at("change_address").at(asset_id).at("address"),
825-
is_liquid ? 1 : 0, asset_id == "btc" ? std::string{} : asset_id);
826-
have_change_output = true;
827-
change_index = tx->num_outputs - 1;
828-
if (is_liquid && include_fee) {
829-
std::swap(tx->outputs[fee_index], tx->outputs[change_index]);
830-
std::swap(fee_index, change_index);
830+
// We have more than the dust amount of change. First look for an explicit change
831+
// output in the addressees and if present send the change there
832+
amount::value_type change_amount = (total - required_total - fee).value();
833+
834+
if (explicit_change_index == NO_CHANGE_INDEX) {
835+
// No explicit change output specified, add a change output using the generated change
836+
// address
837+
add_tx_output(net_params, result, tx, result.at("change_address").at(asset_id).at("address"),
838+
is_liquid ? 1 : 0, asset_id == "btc" ? std::string{} : asset_id);
839+
have_change_output = true;
840+
change_index = tx->num_outputs - 1;
841+
if (is_liquid && include_fee) {
842+
std::swap(tx->outputs[fee_index], tx->outputs[change_index]);
843+
std::swap(fee_index, change_index);
844+
}
845+
} else {
846+
// Use explicit change output
847+
set_tx_output_value(net_params, tx, explicit_change_index, asset_id, change_amount);
848+
auto addressees = *addressees_p;
849+
addressees[explicit_change_index]["satoshi"] = change_amount;
850+
change_index = explicit_change_index;
851+
have_change_output = true;
831852
}
853+
832854
result["have_change"][asset_id] = have_change_output;
833855
result["change_index"][asset_id] = change_index;
834856
}
@@ -850,11 +872,13 @@ namespace sdk {
850872
} else {
851873
auto& change_output = tx->outputs[change_index];
852874
change_output.satoshi = change_amount;
853-
const uint32_t new_change_index = get_uniform_uint32_t(tx->num_outputs);
854-
// Randomize change output
855-
if (change_index != new_change_index) {
856-
std::swap(tx->outputs[new_change_index], change_output);
857-
change_index = new_change_index;
875+
if (explicit_change_index == NO_CHANGE_INDEX) {
876+
// Randomize change output for non-explicit change
877+
const uint32_t new_change_index = get_uniform_uint32_t(tx->num_outputs);
878+
if (change_index != new_change_index) {
879+
std::swap(tx->outputs[new_change_index], change_output);
880+
change_index = new_change_index;
881+
}
858882
}
859883
}
860884
}

src/transaction_utils.cpp

+25-4
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,10 @@ namespace sdk {
359359
}
360360

361361
// Transactions with outputs below the dust threshold (except OP_RETURN)
362-
// are not relayed by network nodes
363-
if (!result.value("send_all", false) && satoshi.value() < session.get_dust_threshold()) {
362+
// are not relayed by network nodes. send_all and explicit change outputs
363+
// have amounts set to zero because they are calculated
364+
if (!result.value("send_all", false) && !json_get_value(addressee, "is_change", false)
365+
&& satoshi.value() < session.get_dust_threshold()) {
364366
result["error"] = res::id_invalid_amount;
365367
}
366368

@@ -371,6 +373,17 @@ namespace sdk {
371373
net_params, result, tx, address, satoshi.value(), asset_id_from_json(net_params, addressee));
372374
}
373375

376+
void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
377+
const std::string& asset_id, amount::value_type satoshi)
378+
{
379+
const bool is_liquid = net_params.is_liquid();
380+
if (is_liquid) {
381+
set_tx_output_commitment(net_params, tx, index, asset_id, satoshi);
382+
} else {
383+
tx->outputs[index].satoshi = satoshi;
384+
}
385+
}
386+
374387
void update_tx_size_info(const wally_tx_ptr& tx, nlohmann::json& result)
375388
{
376389
const bool valid = tx->num_inputs != 0u && tx->num_outputs != 0u;
@@ -439,6 +452,8 @@ namespace sdk {
439452
: false;
440453
const uint32_t change_index
441454
= have_change ? result.at("change_index").at(asset_id).get<uint32_t>() : NO_CHANGE_INDEX;
455+
const bool explicit_change
456+
= have_change ? result.at("change_type").at(asset_id).get<std::string>() == "explicit" : false;
442457

443458
amount::value_type satoshi = o.satoshi;
444459
if (net_params.is_liquid()) {
@@ -461,10 +476,11 @@ namespace sdk {
461476
}
462477
};
463478

479+
464480
if (is_fee) {
465481
// Nothing to do
466-
} else if (i == change_index) {
467-
// Insert our change meta-data for the change output
482+
} else if (i == change_index && !explicit_change) {
483+
// Insert our change meta-data for the generated (non-explicit) change output
468484
const auto& change_address = result.at("change_address").at(asset_id);
469485
output.insert(change_address.begin(), change_address.end());
470486
if (net_params.is_liquid()) {
@@ -477,6 +493,11 @@ namespace sdk {
477493
if (net_params.is_liquid()) {
478494
output["public_key"] = blinding_key_from_addr(address);
479495
}
496+
if (i == change_index && explicit_change) {
497+
// This is an explicit change output (as opposed to one generated by the gdk)
498+
// Mark with 'is_change'
499+
output["is_change"] = true;
500+
}
480501
++addressee_index;
481502
}
482503

src/transaction_utils.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ namespace sdk {
8787
amount add_tx_addressee(ga_session& session, const network_parameters& net_params, nlohmann::json& result,
8888
wally_tx_ptr& tx, nlohmann::json& addressee);
8989

90+
void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
91+
const std::string& asset_id, amount::value_type satoshi);
92+
9093
vbf_t generate_final_vbf(byte_span_t input_abfs, byte_span_t input_vbfs, uint64_span_t input_values,
9194
const std::vector<abf_t>& output_abfs, const std::vector<vbf_t>& output_vbfs, uint32_t num_inputs);
9295

0 commit comments

Comments
 (0)