Skip to content

Commit bae239e

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 15ee861 commit bae239e

File tree

3 files changed

+74
-26
lines changed

3 files changed

+74
-26
lines changed

src/ga_tx.cpp

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

592+
uint32_t explicit_change_index = NO_CHANGE_INDEX;
592593
if (num_addressees) {
594+
size_t addressee_index = 0;
593595
for (auto& addressee : *addressees_p) {
594596
const auto addressee_asset_id = asset_id_from_json(net_params, addressee);
595597
if (addressee_asset_id == asset_id) {
596-
required_total += add_tx_addressee(session, net_params, result, tx, addressee);
598+
const auto amount = add_tx_addressee(session, net_params, result, tx, addressee);
599+
if (!json_get_value(addressee, "is_change", false)) {
600+
required_total += amount;
601+
} else {
602+
if (explicit_change_index != NO_CHANGE_INDEX) {
603+
set_tx_error(result, "Only one explicit change addressee allowed");
604+
break;
605+
}
606+
explicit_change_index = addressee_index;
607+
}
597608
reordered_addressees.push_back(addressee);
598609
}
610+
++addressee_index;
599611
}
600612
}
613+
result["change_type"][asset_id] = explicit_change_index == NO_CHANGE_INDEX ? "generated" : "explicit";
601614

602615
// TODO: filter per asset or assume always single asset
603616
if (manual_selection) {
@@ -734,11 +747,7 @@ namespace sdk {
734747
// so compute what we can send (everything minus the
735748
// fee) and exit the loop
736749
required_total = available_total - fee;
737-
if (is_liquid) {
738-
set_tx_output_commitment(tx, 0, asset_id, required_total.value());
739-
} else {
740-
tx->outputs[0].satoshi = required_total.value();
741-
}
750+
set_tx_output_value(net_params, tx, 0, asset_id, required_total.value());
742751
if (num_addressees == 1u) {
743752
addressees_p->at(0)["satoshi"] = required_total.value();
744753
}
@@ -794,17 +803,30 @@ namespace sdk {
794803
continue;
795804
}
796805

797-
// We have more than the dust amount of change. Add a change
798-
// output to collect it, then loop again in case the amount
799-
// this increases the fee by requires more UTXOs.
800-
const auto change_address = result.at("change_address").at(asset_id).at("address");
801-
add_tx_output(net_params, result, tx, change_address, is_liquid ? 1 : 0, asset_id);
802-
have_change_output = true;
803-
change_index = tx->num_outputs - 1;
804-
if (is_liquid && include_fee) {
805-
std::swap(tx->outputs[fee_index], tx->outputs[change_index]);
806-
std::swap(fee_index, change_index);
806+
// We have more than the dust amount of change. First look for an explicit change
807+
// output in the addressees and if present send the change there
808+
amount::value_type change_amount = (total - required_total - fee).value();
809+
810+
if (explicit_change_index == NO_CHANGE_INDEX) {
811+
// No explicit change output specified, add a change output using the generated change
812+
// address
813+
add_tx_output(net_params, result, tx, result.at("change_address").at(asset_id).at("address"),
814+
is_liquid ? 1 : 0, asset_id == "btc" ? std::string{} : asset_id);
815+
have_change_output = true;
816+
change_index = tx->num_outputs - 1;
817+
if (is_liquid && include_fee) {
818+
std::swap(tx->outputs[fee_index], tx->outputs[change_index]);
819+
std::swap(fee_index, change_index);
820+
}
821+
} else {
822+
// Use explicit change output
823+
set_tx_output_value(net_params, tx, explicit_change_index, asset_id, change_amount);
824+
auto addressees = *addressees_p;
825+
addressees[explicit_change_index]["satoshi"] = change_amount;
826+
change_index = explicit_change_index;
827+
have_change_output = true;
807828
}
829+
808830
result["have_change"][asset_id] = have_change_output;
809831
result["change_index"][asset_id] = change_index;
810832
}
@@ -826,11 +848,13 @@ namespace sdk {
826848
} else {
827849
auto& change_output = tx->outputs[change_index];
828850
change_output.satoshi = change_amount;
829-
const uint32_t new_change_index = get_uniform_uint32_t(tx->num_outputs);
830-
// Randomize change output
831-
if (change_index != new_change_index) {
832-
std::swap(tx->outputs[new_change_index], change_output);
833-
change_index = new_change_index;
851+
if (explicit_change_index == NO_CHANGE_INDEX) {
852+
// Randomize change output for non-explicit change
853+
const uint32_t new_change_index = get_uniform_uint32_t(tx->num_outputs);
854+
if (change_index != new_change_index) {
855+
std::swap(tx->outputs[new_change_index], change_output);
856+
change_index = new_change_index;
857+
}
834858
}
835859
}
836860
}

src/transaction_utils.cpp

+26-5
Original file line numberDiff line numberDiff line change
@@ -408,9 +408,11 @@ namespace sdk {
408408
}
409409

410410
// Transactions with outputs below the dust threshold (except OP_RETURN)
411-
// are not relayed by network nodes
412-
if (!result.value("send_all", false) && satoshi.value() < session.get_dust_threshold()) {
413-
set_tx_error(result, res::id_invalid_amount);
411+
// are not relayed by network nodes. send_all and explicit change outputs
412+
// have amounts set to zero because they are calculated
413+
if (!result.value("send_all", false) && !json_get_value(addressee, "is_change", false)
414+
&& satoshi.value() < session.get_dust_threshold()) {
415+
result["error"] = res::id_invalid_amount;
414416
}
415417

416418
amount::strip_non_satoshi_keys(addressee);
@@ -420,6 +422,17 @@ namespace sdk {
420422
net_params, result, tx, address, satoshi.value(), asset_id_from_json(net_params, addressee));
421423
}
422424

425+
void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
426+
const std::string& asset_id, amount::value_type satoshi)
427+
{
428+
const bool is_liquid = net_params.is_liquid();
429+
if (is_liquid) {
430+
set_tx_output_commitment(tx, index, asset_id, satoshi);
431+
} else {
432+
tx->outputs[index].satoshi = satoshi;
433+
}
434+
}
435+
423436
void update_tx_size_info(const wally_tx_ptr& tx, nlohmann::json& result)
424437
{
425438
const bool valid = tx->num_inputs != 0u && tx->num_outputs != 0u;
@@ -491,6 +504,8 @@ namespace sdk {
491504
: false;
492505
const uint32_t change_index
493506
= have_change ? result.at("change_index").at(asset_id).get<uint32_t>() : NO_CHANGE_INDEX;
507+
const bool explicit_change
508+
= have_change ? result.at("change_type").at(asset_id).get<std::string>() == "explicit" : false;
494509

495510
amount::value_type satoshi = o.satoshi;
496511
if (is_liquid) {
@@ -511,10 +526,11 @@ namespace sdk {
511526
}
512527
};
513528

529+
514530
if (is_fee) {
515531
// Nothing to do
516-
} else if (i == change_index) {
517-
// Insert our change meta-data for the change output
532+
} else if (i == change_index && !explicit_change) {
533+
// Insert our change meta-data for the generated (non-explicit) change output
518534
const auto& change_address = result.at("change_address").at(asset_id);
519535
output.insert(change_address.begin(), change_address.end());
520536
if (is_liquid) {
@@ -527,6 +543,11 @@ namespace sdk {
527543
if (is_liquid) {
528544
output["public_key"] = blinding_key_from_addr(address);
529545
}
546+
if (i == change_index && explicit_change) {
547+
// This is an explicit change output (as opposed to one generated by the gdk)
548+
// Mark with 'is_change'
549+
output["is_change"] = true;
550+
}
530551
++addressee_index;
531552
}
532553

src/transaction_utils.hpp

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

96+
void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
97+
const std::string& asset_id, amount::value_type satoshi);
98+
9699
vbf_t generate_final_vbf(byte_span_t input_abfs, byte_span_t input_vbfs, uint64_span_t input_values,
97100
const std::vector<abf_t>& output_abfs, const std::vector<vbf_t>& output_vbfs, uint32_t num_inputs);
98101

0 commit comments

Comments
 (0)