diff --git a/Paymetheus.Decred/Wallet/TransactionFees.cs b/Paymetheus.Decred/Wallet/TransactionFees.cs
index 436454a..6fb7076 100644
--- a/Paymetheus.Decred/Wallet/TransactionFees.cs
+++ b/Paymetheus.Decred/Wallet/TransactionFees.cs
@@ -72,7 +72,7 @@ public static Transaction AddChange(Transaction tx, Amount totalInput, OutputScr
var totalNonChangeOutput = tx.Outputs.Sum(o => o.Amount);
var changeAmount = totalInput - totalNonChangeOutput - feeEstimate;
- var changeOutput = new Transaction.Output(changeAmount, Transaction.SupportedVersion, changeScript.Script);
+ var changeOutput = new Transaction.Output(changeAmount, Transaction.Output.LatestPkScriptVersion, changeScript.Script);
// Change should not be created if the change output itself would be considered dust.
if (TransactionRules.IsDust(changeOutput, feePerKb))
diff --git a/Paymetheus.Decred/Wallet/UnspentOutput.cs b/Paymetheus.Decred/Wallet/UnspentOutput.cs
index 2af54ad..7559b41 100644
--- a/Paymetheus.Decred/Wallet/UnspentOutput.cs
+++ b/Paymetheus.Decred/Wallet/UnspentOutput.cs
@@ -9,7 +9,7 @@ namespace Paymetheus.Decred.Wallet
{
public sealed class UnspentOutput
{
- public UnspentOutput(Blake256Hash txHash, uint outputIndex, Amount amount, OutputScript pkScript, DateTimeOffset seenTime, bool isFromCoinbase)
+ public UnspentOutput(Blake256Hash txHash, uint outputIndex, byte tree, Amount amount, OutputScript pkScript, DateTimeOffset seenTime, bool isFromCoinbase)
{
if (txHash == null)
throw new ArgumentNullException(nameof(txHash));
@@ -18,6 +18,7 @@ public UnspentOutput(Blake256Hash txHash, uint outputIndex, Amount amount, Outpu
TransactionHash = txHash;
OutputIndex = outputIndex;
+ Tree = tree;
Amount = amount;
PkScript = pkScript;
SeenTime = seenTime;
@@ -26,6 +27,7 @@ public UnspentOutput(Blake256Hash txHash, uint outputIndex, Amount amount, Outpu
public Blake256Hash TransactionHash { get; }
public uint OutputIndex { get; }
+ public byte Tree { get; }
public Amount Amount { get; }
public OutputScript PkScript { get; }
public DateTimeOffset SeenTime { get; }
diff --git a/Paymetheus.Rpc/Api.cs b/Paymetheus.Rpc/Api.cs
index 74a7a70..baf87f3 100644
--- a/Paymetheus.Rpc/Api.cs
+++ b/Paymetheus.Rpc/Api.cs
@@ -79,91 +79,91 @@ static ApiReflection() {
"b25SZXF1ZXN0Eg8KB2FjY291bnQYASABKA0SFQoNdGFyZ2V0X2Ftb3VudBgC",
"IAEoAxIeChZyZXF1aXJlZF9jb25maXJtYXRpb25zGAMgASgFEiIKGmluY2x1",
"ZGVfaW1tYXR1cmVfY29pbmJhc2VzGAQgASgIEh0KFWluY2x1ZGVfY2hhbmdl",
- "X3NjcmlwdBgFIAEoCCKpAgoXRnVuZFRyYW5zYWN0aW9uUmVzcG9uc2USSwoQ",
+ "X3NjcmlwdBgFIAEoCCK3AgoXRnVuZFRyYW5zYWN0aW9uUmVzcG9uc2USSwoQ",
"c2VsZWN0ZWRfb3V0cHV0cxgBIAMoCzIxLndhbGxldHJwYy5GdW5kVHJhbnNh",
"Y3Rpb25SZXNwb25zZS5QcmV2aW91c091dHB1dBIUCgx0b3RhbF9hbW91bnQY",
- "AiABKAMSGAoQY2hhbmdlX3BrX3NjcmlwdBgDIAEoDBqQAQoOUHJldmlvdXNP",
+ "AiABKAMSGAoQY2hhbmdlX3BrX3NjcmlwdBgDIAEoDBqeAQoOUHJldmlvdXNP",
"dXRwdXQSGAoQdHJhbnNhY3Rpb25faGFzaBgBIAEoDBIUCgxvdXRwdXRfaW5k",
"ZXgYAiABKA0SDgoGYW1vdW50GAMgASgDEhEKCXBrX3NjcmlwdBgEIAEoDBIU",
- "CgxyZWNlaXZlX3RpbWUYBSABKAMSFQoNZnJvbV9jb2luYmFzZRgGIAEoCCJj",
- "ChZTaWduVHJhbnNhY3Rpb25SZXF1ZXN0EhIKCnBhc3NwaHJhc2UYASABKAwS",
- "HgoWc2VyaWFsaXplZF90cmFuc2FjdGlvbhgCIAEoDBIVCg1pbnB1dF9pbmRl",
- "eGVzGAMgAygNIk4KF1NpZ25UcmFuc2FjdGlvblJlc3BvbnNlEhMKC3RyYW5z",
- "YWN0aW9uGAEgASgMEh4KFnVuc2lnbmVkX2lucHV0X2luZGV4ZXMYAiADKA0i",
- "NwoZUHVibGlzaFRyYW5zYWN0aW9uUmVxdWVzdBIaChJzaWduZWRfdHJhbnNh",
- "Y3Rpb24YASABKAwiHAoaUHVibGlzaFRyYW5zYWN0aW9uUmVzcG9uc2UiIQof",
- "VHJhbnNhY3Rpb25Ob3RpZmljYXRpb25zUmVxdWVzdCLOAQogVHJhbnNhY3Rp",
- "b25Ob3RpZmljYXRpb25zUmVzcG9uc2USMAoPYXR0YWNoZWRfYmxvY2tzGAEg",
- "AygLMhcud2FsbGV0cnBjLkJsb2NrRGV0YWlscxIXCg9kZXRhY2hlZF9ibG9j",
- "a3MYAiADKAwSOwoUdW5taW5lZF90cmFuc2FjdGlvbnMYAyADKAsyHS53YWxs",
- "ZXRycGMuVHJhbnNhY3Rpb25EZXRhaWxzEiIKGnVubWluZWRfdHJhbnNhY3Rp",
- "b25faGFzaGVzGAQgAygMImQKHVNwZW50bmVzc05vdGlmaWNhdGlvbnNSZXF1",
- "ZXN0Eg8KB2FjY291bnQYASABKA0SGQoRbm9fbm90aWZ5X3Vuc3BlbnQYAiAB",
- "KAgSFwoPbm9fbm90aWZ5X3NwZW50GAMgASgIIs4BCh5TcGVudG5lc3NOb3Rp",
- "ZmljYXRpb25zUmVzcG9uc2USGAoQdHJhbnNhY3Rpb25faGFzaBgBIAEoDBIU",
- "CgxvdXRwdXRfaW5kZXgYAiABKA0SQgoHc3BlbmRlchgDIAEoCzIxLndhbGxl",
- "dHJwYy5TcGVudG5lc3NOb3RpZmljYXRpb25zUmVzcG9uc2UuU3BlbmRlcho4",
- "CgdTcGVuZGVyEhgKEHRyYW5zYWN0aW9uX2hhc2gYASABKAwSEwoLaW5wdXRf",
- "aW5kZXgYAiABKA0iHQobQWNjb3VudE5vdGlmaWNhdGlvbnNSZXF1ZXN0IqAB",
- "ChxBY2NvdW50Tm90aWZpY2F0aW9uc1Jlc3BvbnNlEhYKDmFjY291bnRfbnVt",
- "YmVyGAEgASgNEhQKDGFjY291bnRfbmFtZRgCIAEoCRIaChJleHRlcm5hbF9r",
- "ZXlfY291bnQYAyABKA0SGgoSaW50ZXJuYWxfa2V5X2NvdW50GAQgASgNEhoK",
- "EmltcG9ydGVkX2tleV9jb3VudBgFIAEoDSJaChNDcmVhdGVXYWxsZXRSZXF1",
- "ZXN0EhkKEXB1YmxpY19wYXNzcGhyYXNlGAEgASgMEhoKEnByaXZhdGVfcGFz",
- "c3BocmFzZRgCIAEoDBIMCgRzZWVkGAMgASgMIhYKFENyZWF0ZVdhbGxldFJl",
- "c3BvbnNlIi4KEU9wZW5XYWxsZXRSZXF1ZXN0EhkKEXB1YmxpY19wYXNzcGhy",
- "YXNlGAEgASgMIhQKEk9wZW5XYWxsZXRSZXNwb25zZSIUChJDbG9zZVdhbGxl",
- "dFJlcXVlc3QiFQoTQ2xvc2VXYWxsZXRSZXNwb25zZSIVChNXYWxsZXRFeGlz",
- "dHNSZXF1ZXN0IiYKFFdhbGxldEV4aXN0c1Jlc3BvbnNlEg4KBmV4aXN0cxgB",
- "IAEoCCJsChhTdGFydENvbnNlbnN1c1JwY1JlcXVlc3QSFwoPbmV0d29ya19h",
- "ZGRyZXNzGAEgASgJEhAKCHVzZXJuYW1lGAIgASgJEhAKCHBhc3N3b3JkGAMg",
- "ASgMEhMKC2NlcnRpZmljYXRlGAQgASgMIhsKGVN0YXJ0Q29uc2Vuc3VzUnBj",
- "UmVzcG9uc2UyUgoOVmVyc2lvblNlcnZpY2USQAoHVmVyc2lvbhIZLndhbGxl",
- "dHJwYy5WZXJzaW9uUmVxdWVzdBoaLndhbGxldHJwYy5WZXJzaW9uUmVzcG9u",
- "c2Uy0wsKDVdhbGxldFNlcnZpY2USNwoEUGluZxIWLndhbGxldHJwYy5QaW5n",
- "UmVxdWVzdBoXLndhbGxldHJwYy5QaW5nUmVzcG9uc2USQAoHTmV0d29yaxIZ",
- "LndhbGxldHJwYy5OZXR3b3JrUmVxdWVzdBoaLndhbGxldHJwYy5OZXR3b3Jr",
- "UmVzcG9uc2USUgoNQWNjb3VudE51bWJlchIfLndhbGxldHJwYy5BY2NvdW50",
- "TnVtYmVyUmVxdWVzdBogLndhbGxldHJwYy5BY2NvdW50TnVtYmVyUmVzcG9u",
- "c2USQwoIQWNjb3VudHMSGi53YWxsZXRycGMuQWNjb3VudHNSZXF1ZXN0Ghsu",
- "d2FsbGV0cnBjLkFjY291bnRzUmVzcG9uc2USQAoHQmFsYW5jZRIZLndhbGxl",
- "dHJwYy5CYWxhbmNlUmVxdWVzdBoaLndhbGxldHJwYy5CYWxhbmNlUmVzcG9u",
- "c2USWAoPR2V0VHJhbnNhY3Rpb25zEiEud2FsbGV0cnBjLkdldFRyYW5zYWN0",
- "aW9uc1JlcXVlc3QaIi53YWxsZXRycGMuR2V0VHJhbnNhY3Rpb25zUmVzcG9u",
- "c2USdQoYVHJhbnNhY3Rpb25Ob3RpZmljYXRpb25zEioud2FsbGV0cnBjLlRy",
- "YW5zYWN0aW9uTm90aWZpY2F0aW9uc1JlcXVlc3QaKy53YWxsZXRycGMuVHJh",
- "bnNhY3Rpb25Ob3RpZmljYXRpb25zUmVzcG9uc2UwARJvChZTcGVudG5lc3NO",
- "b3RpZmljYXRpb25zEigud2FsbGV0cnBjLlNwZW50bmVzc05vdGlmaWNhdGlv",
- "bnNSZXF1ZXN0Gikud2FsbGV0cnBjLlNwZW50bmVzc05vdGlmaWNhdGlvbnNS",
- "ZXNwb25zZTABEmkKFEFjY291bnROb3RpZmljYXRpb25zEiYud2FsbGV0cnBj",
- "LkFjY291bnROb3RpZmljYXRpb25zUmVxdWVzdBonLndhbGxldHJwYy5BY2Nv",
- "dW50Tm90aWZpY2F0aW9uc1Jlc3BvbnNlMAESWwoQQ2hhbmdlUGFzc3BocmFz",
- "ZRIiLndhbGxldHJwYy5DaGFuZ2VQYXNzcGhyYXNlUmVxdWVzdBojLndhbGxl",
- "dHJwYy5DaGFuZ2VQYXNzcGhyYXNlUmVzcG9uc2USUgoNUmVuYW1lQWNjb3Vu",
- "dBIfLndhbGxldHJwYy5SZW5hbWVBY2NvdW50UmVxdWVzdBogLndhbGxldHJw",
- "Yy5SZW5hbWVBY2NvdW50UmVzcG9uc2USTAoLTmV4dEFjY291bnQSHS53YWxs",
- "ZXRycGMuTmV4dEFjY291bnRSZXF1ZXN0Gh4ud2FsbGV0cnBjLk5leHRBY2Nv",
- "dW50UmVzcG9uc2USTAoLTmV4dEFkZHJlc3MSHS53YWxsZXRycGMuTmV4dEFk",
- "ZHJlc3NSZXF1ZXN0Gh4ud2FsbGV0cnBjLk5leHRBZGRyZXNzUmVzcG9uc2US",
- "WwoQSW1wb3J0UHJpdmF0ZUtleRIiLndhbGxldHJwYy5JbXBvcnRQcml2YXRl",
- "S2V5UmVxdWVzdBojLndhbGxldHJwYy5JbXBvcnRQcml2YXRlS2V5UmVzcG9u",
- "c2USWAoPRnVuZFRyYW5zYWN0aW9uEiEud2FsbGV0cnBjLkZ1bmRUcmFuc2Fj",
- "dGlvblJlcXVlc3QaIi53YWxsZXRycGMuRnVuZFRyYW5zYWN0aW9uUmVzcG9u",
- "c2USWAoPU2lnblRyYW5zYWN0aW9uEiEud2FsbGV0cnBjLlNpZ25UcmFuc2Fj",
- "dGlvblJlcXVlc3QaIi53YWxsZXRycGMuU2lnblRyYW5zYWN0aW9uUmVzcG9u",
- "c2USYQoSUHVibGlzaFRyYW5zYWN0aW9uEiQud2FsbGV0cnBjLlB1Ymxpc2hU",
- "cmFuc2FjdGlvblJlcXVlc3QaJS53YWxsZXRycGMuUHVibGlzaFRyYW5zYWN0",
- "aW9uUmVzcG9uc2UysAMKE1dhbGxldExvYWRlclNlcnZpY2USTwoMV2FsbGV0",
- "RXhpc3RzEh4ud2FsbGV0cnBjLldhbGxldEV4aXN0c1JlcXVlc3QaHy53YWxs",
- "ZXRycGMuV2FsbGV0RXhpc3RzUmVzcG9uc2USTwoMQ3JlYXRlV2FsbGV0Eh4u",
- "d2FsbGV0cnBjLkNyZWF0ZVdhbGxldFJlcXVlc3QaHy53YWxsZXRycGMuQ3Jl",
- "YXRlV2FsbGV0UmVzcG9uc2USSQoKT3BlbldhbGxldBIcLndhbGxldHJwYy5P",
- "cGVuV2FsbGV0UmVxdWVzdBodLndhbGxldHJwYy5PcGVuV2FsbGV0UmVzcG9u",
- "c2USTAoLQ2xvc2VXYWxsZXQSHS53YWxsZXRycGMuQ2xvc2VXYWxsZXRSZXF1",
- "ZXN0Gh4ud2FsbGV0cnBjLkNsb3NlV2FsbGV0UmVzcG9uc2USXgoRU3RhcnRD",
- "b25zZW5zdXNScGMSIy53YWxsZXRycGMuU3RhcnRDb25zZW5zdXNScGNSZXF1",
- "ZXN0GiQud2FsbGV0cnBjLlN0YXJ0Q29uc2Vuc3VzUnBjUmVzcG9uc2ViBnBy",
- "b3RvMw=="));
+ "CgxyZWNlaXZlX3RpbWUYBSABKAMSFQoNZnJvbV9jb2luYmFzZRgGIAEoCBIM",
+ "CgR0cmVlGAcgASgFImMKFlNpZ25UcmFuc2FjdGlvblJlcXVlc3QSEgoKcGFz",
+ "c3BocmFzZRgBIAEoDBIeChZzZXJpYWxpemVkX3RyYW5zYWN0aW9uGAIgASgM",
+ "EhUKDWlucHV0X2luZGV4ZXMYAyADKA0iTgoXU2lnblRyYW5zYWN0aW9uUmVz",
+ "cG9uc2USEwoLdHJhbnNhY3Rpb24YASABKAwSHgoWdW5zaWduZWRfaW5wdXRf",
+ "aW5kZXhlcxgCIAMoDSI3ChlQdWJsaXNoVHJhbnNhY3Rpb25SZXF1ZXN0EhoK",
+ "EnNpZ25lZF90cmFuc2FjdGlvbhgBIAEoDCIcChpQdWJsaXNoVHJhbnNhY3Rp",
+ "b25SZXNwb25zZSIhCh9UcmFuc2FjdGlvbk5vdGlmaWNhdGlvbnNSZXF1ZXN0",
+ "Is4BCiBUcmFuc2FjdGlvbk5vdGlmaWNhdGlvbnNSZXNwb25zZRIwCg9hdHRh",
+ "Y2hlZF9ibG9ja3MYASADKAsyFy53YWxsZXRycGMuQmxvY2tEZXRhaWxzEhcK",
+ "D2RldGFjaGVkX2Jsb2NrcxgCIAMoDBI7ChR1bm1pbmVkX3RyYW5zYWN0aW9u",
+ "cxgDIAMoCzIdLndhbGxldHJwYy5UcmFuc2FjdGlvbkRldGFpbHMSIgoadW5t",
+ "aW5lZF90cmFuc2FjdGlvbl9oYXNoZXMYBCADKAwiZAodU3BlbnRuZXNzTm90",
+ "aWZpY2F0aW9uc1JlcXVlc3QSDwoHYWNjb3VudBgBIAEoDRIZChFub19ub3Rp",
+ "ZnlfdW5zcGVudBgCIAEoCBIXCg9ub19ub3RpZnlfc3BlbnQYAyABKAgizgEK",
+ "HlNwZW50bmVzc05vdGlmaWNhdGlvbnNSZXNwb25zZRIYChB0cmFuc2FjdGlv",
+ "bl9oYXNoGAEgASgMEhQKDG91dHB1dF9pbmRleBgCIAEoDRJCCgdzcGVuZGVy",
+ "GAMgASgLMjEud2FsbGV0cnBjLlNwZW50bmVzc05vdGlmaWNhdGlvbnNSZXNw",
+ "b25zZS5TcGVuZGVyGjgKB1NwZW5kZXISGAoQdHJhbnNhY3Rpb25faGFzaBgB",
+ "IAEoDBITCgtpbnB1dF9pbmRleBgCIAEoDSIdChtBY2NvdW50Tm90aWZpY2F0",
+ "aW9uc1JlcXVlc3QioAEKHEFjY291bnROb3RpZmljYXRpb25zUmVzcG9uc2US",
+ "FgoOYWNjb3VudF9udW1iZXIYASABKA0SFAoMYWNjb3VudF9uYW1lGAIgASgJ",
+ "EhoKEmV4dGVybmFsX2tleV9jb3VudBgDIAEoDRIaChJpbnRlcm5hbF9rZXlf",
+ "Y291bnQYBCABKA0SGgoSaW1wb3J0ZWRfa2V5X2NvdW50GAUgASgNIloKE0Ny",
+ "ZWF0ZVdhbGxldFJlcXVlc3QSGQoRcHVibGljX3Bhc3NwaHJhc2UYASABKAwS",
+ "GgoScHJpdmF0ZV9wYXNzcGhyYXNlGAIgASgMEgwKBHNlZWQYAyABKAwiFgoU",
+ "Q3JlYXRlV2FsbGV0UmVzcG9uc2UiLgoRT3BlbldhbGxldFJlcXVlc3QSGQoR",
+ "cHVibGljX3Bhc3NwaHJhc2UYASABKAwiFAoST3BlbldhbGxldFJlc3BvbnNl",
+ "IhQKEkNsb3NlV2FsbGV0UmVxdWVzdCIVChNDbG9zZVdhbGxldFJlc3BvbnNl",
+ "IhUKE1dhbGxldEV4aXN0c1JlcXVlc3QiJgoUV2FsbGV0RXhpc3RzUmVzcG9u",
+ "c2USDgoGZXhpc3RzGAEgASgIImwKGFN0YXJ0Q29uc2Vuc3VzUnBjUmVxdWVz",
+ "dBIXCg9uZXR3b3JrX2FkZHJlc3MYASABKAkSEAoIdXNlcm5hbWUYAiABKAkS",
+ "EAoIcGFzc3dvcmQYAyABKAwSEwoLY2VydGlmaWNhdGUYBCABKAwiGwoZU3Rh",
+ "cnRDb25zZW5zdXNScGNSZXNwb25zZTJSCg5WZXJzaW9uU2VydmljZRJACgdW",
+ "ZXJzaW9uEhkud2FsbGV0cnBjLlZlcnNpb25SZXF1ZXN0Ghoud2FsbGV0cnBj",
+ "LlZlcnNpb25SZXNwb25zZTLTCwoNV2FsbGV0U2VydmljZRI3CgRQaW5nEhYu",
+ "d2FsbGV0cnBjLlBpbmdSZXF1ZXN0Ghcud2FsbGV0cnBjLlBpbmdSZXNwb25z",
+ "ZRJACgdOZXR3b3JrEhkud2FsbGV0cnBjLk5ldHdvcmtSZXF1ZXN0Ghoud2Fs",
+ "bGV0cnBjLk5ldHdvcmtSZXNwb25zZRJSCg1BY2NvdW50TnVtYmVyEh8ud2Fs",
+ "bGV0cnBjLkFjY291bnROdW1iZXJSZXF1ZXN0GiAud2FsbGV0cnBjLkFjY291",
+ "bnROdW1iZXJSZXNwb25zZRJDCghBY2NvdW50cxIaLndhbGxldHJwYy5BY2Nv",
+ "dW50c1JlcXVlc3QaGy53YWxsZXRycGMuQWNjb3VudHNSZXNwb25zZRJACgdC",
+ "YWxhbmNlEhkud2FsbGV0cnBjLkJhbGFuY2VSZXF1ZXN0Ghoud2FsbGV0cnBj",
+ "LkJhbGFuY2VSZXNwb25zZRJYCg9HZXRUcmFuc2FjdGlvbnMSIS53YWxsZXRy",
+ "cGMuR2V0VHJhbnNhY3Rpb25zUmVxdWVzdBoiLndhbGxldHJwYy5HZXRUcmFu",
+ "c2FjdGlvbnNSZXNwb25zZRJ1ChhUcmFuc2FjdGlvbk5vdGlmaWNhdGlvbnMS",
+ "Ki53YWxsZXRycGMuVHJhbnNhY3Rpb25Ob3RpZmljYXRpb25zUmVxdWVzdBor",
+ "LndhbGxldHJwYy5UcmFuc2FjdGlvbk5vdGlmaWNhdGlvbnNSZXNwb25zZTAB",
+ "Em8KFlNwZW50bmVzc05vdGlmaWNhdGlvbnMSKC53YWxsZXRycGMuU3BlbnRu",
+ "ZXNzTm90aWZpY2F0aW9uc1JlcXVlc3QaKS53YWxsZXRycGMuU3BlbnRuZXNz",
+ "Tm90aWZpY2F0aW9uc1Jlc3BvbnNlMAESaQoUQWNjb3VudE5vdGlmaWNhdGlv",
+ "bnMSJi53YWxsZXRycGMuQWNjb3VudE5vdGlmaWNhdGlvbnNSZXF1ZXN0Gicu",
+ "d2FsbGV0cnBjLkFjY291bnROb3RpZmljYXRpb25zUmVzcG9uc2UwARJbChBD",
+ "aGFuZ2VQYXNzcGhyYXNlEiIud2FsbGV0cnBjLkNoYW5nZVBhc3NwaHJhc2VS",
+ "ZXF1ZXN0GiMud2FsbGV0cnBjLkNoYW5nZVBhc3NwaHJhc2VSZXNwb25zZRJS",
+ "Cg1SZW5hbWVBY2NvdW50Eh8ud2FsbGV0cnBjLlJlbmFtZUFjY291bnRSZXF1",
+ "ZXN0GiAud2FsbGV0cnBjLlJlbmFtZUFjY291bnRSZXNwb25zZRJMCgtOZXh0",
+ "QWNjb3VudBIdLndhbGxldHJwYy5OZXh0QWNjb3VudFJlcXVlc3QaHi53YWxs",
+ "ZXRycGMuTmV4dEFjY291bnRSZXNwb25zZRJMCgtOZXh0QWRkcmVzcxIdLndh",
+ "bGxldHJwYy5OZXh0QWRkcmVzc1JlcXVlc3QaHi53YWxsZXRycGMuTmV4dEFk",
+ "ZHJlc3NSZXNwb25zZRJbChBJbXBvcnRQcml2YXRlS2V5EiIud2FsbGV0cnBj",
+ "LkltcG9ydFByaXZhdGVLZXlSZXF1ZXN0GiMud2FsbGV0cnBjLkltcG9ydFBy",
+ "aXZhdGVLZXlSZXNwb25zZRJYCg9GdW5kVHJhbnNhY3Rpb24SIS53YWxsZXRy",
+ "cGMuRnVuZFRyYW5zYWN0aW9uUmVxdWVzdBoiLndhbGxldHJwYy5GdW5kVHJh",
+ "bnNhY3Rpb25SZXNwb25zZRJYCg9TaWduVHJhbnNhY3Rpb24SIS53YWxsZXRy",
+ "cGMuU2lnblRyYW5zYWN0aW9uUmVxdWVzdBoiLndhbGxldHJwYy5TaWduVHJh",
+ "bnNhY3Rpb25SZXNwb25zZRJhChJQdWJsaXNoVHJhbnNhY3Rpb24SJC53YWxs",
+ "ZXRycGMuUHVibGlzaFRyYW5zYWN0aW9uUmVxdWVzdBolLndhbGxldHJwYy5Q",
+ "dWJsaXNoVHJhbnNhY3Rpb25SZXNwb25zZTKwAwoTV2FsbGV0TG9hZGVyU2Vy",
+ "dmljZRJPCgxXYWxsZXRFeGlzdHMSHi53YWxsZXRycGMuV2FsbGV0RXhpc3Rz",
+ "UmVxdWVzdBofLndhbGxldHJwYy5XYWxsZXRFeGlzdHNSZXNwb25zZRJPCgxD",
+ "cmVhdGVXYWxsZXQSHi53YWxsZXRycGMuQ3JlYXRlV2FsbGV0UmVxdWVzdBof",
+ "LndhbGxldHJwYy5DcmVhdGVXYWxsZXRSZXNwb25zZRJJCgpPcGVuV2FsbGV0",
+ "Ehwud2FsbGV0cnBjLk9wZW5XYWxsZXRSZXF1ZXN0Gh0ud2FsbGV0cnBjLk9w",
+ "ZW5XYWxsZXRSZXNwb25zZRJMCgtDbG9zZVdhbGxldBIdLndhbGxldHJwYy5D",
+ "bG9zZVdhbGxldFJlcXVlc3QaHi53YWxsZXRycGMuQ2xvc2VXYWxsZXRSZXNw",
+ "b25zZRJeChFTdGFydENvbnNlbnN1c1JwYxIjLndhbGxldHJwYy5TdGFydENv",
+ "bnNlbnN1c1JwY1JlcXVlc3QaJC53YWxsZXRycGMuU3RhcnRDb25zZW5zdXNS",
+ "cGNSZXNwb25zZWIGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] {
@@ -196,7 +196,7 @@ static ApiReflection() {
new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.ChangePassphraseRequest), global::Walletrpc.ChangePassphraseRequest.Parser, new[]{ "Key", "OldPassphrase", "NewPassphrase" }, null, new[]{ typeof(global::Walletrpc.ChangePassphraseRequest.Types.Key) }, null),
new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.ChangePassphraseResponse), global::Walletrpc.ChangePassphraseResponse.Parser, null, null, null, null),
new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.FundTransactionRequest), global::Walletrpc.FundTransactionRequest.Parser, new[]{ "Account", "TargetAmount", "RequiredConfirmations", "IncludeImmatureCoinbases", "IncludeChangeScript" }, null, null, null),
- new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.FundTransactionResponse), global::Walletrpc.FundTransactionResponse.Parser, new[]{ "SelectedOutputs", "TotalAmount", "ChangePkScript" }, null, null, new pbr::GeneratedCodeInfo[] { new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.FundTransactionResponse.Types.PreviousOutput), global::Walletrpc.FundTransactionResponse.Types.PreviousOutput.Parser, new[]{ "TransactionHash", "OutputIndex", "Amount", "PkScript", "ReceiveTime", "FromCoinbase" }, null, null, null)}),
+ new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.FundTransactionResponse), global::Walletrpc.FundTransactionResponse.Parser, new[]{ "SelectedOutputs", "TotalAmount", "ChangePkScript" }, null, null, new pbr::GeneratedCodeInfo[] { new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.FundTransactionResponse.Types.PreviousOutput), global::Walletrpc.FundTransactionResponse.Types.PreviousOutput.Parser, new[]{ "TransactionHash", "OutputIndex", "Amount", "PkScript", "ReceiveTime", "FromCoinbase", "Tree" }, null, null, null)}),
new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.SignTransactionRequest), global::Walletrpc.SignTransactionRequest.Parser, new[]{ "Passphrase", "SerializedTransaction", "InputIndexes" }, null, null, null),
new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.SignTransactionResponse), global::Walletrpc.SignTransactionResponse.Parser, new[]{ "Transaction", "UnsignedInputIndexes" }, null, null, null),
new pbr::GeneratedCodeInfo(typeof(global::Walletrpc.PublishTransactionRequest), global::Walletrpc.PublishTransactionRequest.Parser, new[]{ "SignedTransaction" }, null, null, null),
@@ -4606,6 +4606,7 @@ public PreviousOutput(PreviousOutput other) : this() {
pkScript_ = other.pkScript_;
receiveTime_ = other.receiveTime_;
fromCoinbase_ = other.fromCoinbase_;
+ tree_ = other.tree_;
}
public PreviousOutput Clone() {
@@ -4672,6 +4673,16 @@ public bool FromCoinbase {
}
}
+ /// Field number for the "tree" field.
+ public const int TreeFieldNumber = 7;
+ private int tree_;
+ public int Tree {
+ get { return tree_; }
+ set {
+ tree_ = value;
+ }
+ }
+
public override bool Equals(object other) {
return Equals(other as PreviousOutput);
}
@@ -4689,6 +4700,7 @@ public bool Equals(PreviousOutput other) {
if (PkScript != other.PkScript) return false;
if (ReceiveTime != other.ReceiveTime) return false;
if (FromCoinbase != other.FromCoinbase) return false;
+ if (Tree != other.Tree) return false;
return true;
}
@@ -4700,6 +4712,7 @@ public override int GetHashCode() {
if (PkScript.Length != 0) hash ^= PkScript.GetHashCode();
if (ReceiveTime != 0L) hash ^= ReceiveTime.GetHashCode();
if (FromCoinbase != false) hash ^= FromCoinbase.GetHashCode();
+ if (Tree != 0) hash ^= Tree.GetHashCode();
return hash;
}
@@ -4732,6 +4745,10 @@ public void WriteTo(pb::CodedOutputStream output) {
output.WriteRawTag(48);
output.WriteBool(FromCoinbase);
}
+ if (Tree != 0) {
+ output.WriteRawTag(56);
+ output.WriteInt32(Tree);
+ }
}
public int CalculateSize() {
@@ -4754,6 +4771,9 @@ public int CalculateSize() {
if (FromCoinbase != false) {
size += 1 + 1;
}
+ if (Tree != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Tree);
+ }
return size;
}
@@ -4779,6 +4799,9 @@ public void MergeFrom(PreviousOutput other) {
if (other.FromCoinbase != false) {
FromCoinbase = other.FromCoinbase;
}
+ if (other.Tree != 0) {
+ Tree = other.Tree;
+ }
}
public void MergeFrom(pb::CodedInputStream input) {
@@ -4812,6 +4835,10 @@ public void MergeFrom(pb::CodedInputStream input) {
FromCoinbase = input.ReadBool();
break;
}
+ case 56: {
+ Tree = input.ReadInt32();
+ break;
+ }
}
}
}
diff --git a/Paymetheus.Rpc/Marshalers.cs b/Paymetheus.Rpc/Marshalers.cs
index 0c00a1b..dfdfdfe 100644
--- a/Paymetheus.Rpc/Marshalers.cs
+++ b/Paymetheus.Rpc/Marshalers.cs
@@ -66,12 +66,13 @@ public static UnspentOutput MarshalUnspentOutput(FundTransactionResponse.Types.P
{
var txHash = new Blake256Hash(o.TransactionHash.ToByteArray());
var outputIndex = o.OutputIndex;
+ var tree = (byte)o.Tree;
var amount = (Amount)o.Amount;
var pkScript = OutputScript.ParseScript(o.PkScript.ToByteArray());
var seenTime = DateTimeOffsetExtras.FromUnixTimeSeconds(o.ReceiveTime);
var isFromCoinbase = o.FromCoinbase;
- return new UnspentOutput(txHash, outputIndex, amount, pkScript, seenTime, isFromCoinbase);
+ return new UnspentOutput(txHash, outputIndex, tree, amount, pkScript, seenTime, isFromCoinbase);
}
}
}
diff --git a/Paymetheus.Rpc/WalletClient.cs b/Paymetheus.Rpc/WalletClient.cs
index c1ec126..938e836 100644
--- a/Paymetheus.Rpc/WalletClient.cs
+++ b/Paymetheus.Rpc/WalletClient.cs
@@ -5,7 +5,6 @@
using Google.Protobuf;
using Grpc.Core;
using Paymetheus.Decred;
-using Paymetheus.Decred.Script;
using Paymetheus.Decred.Wallet;
using System;
using System.Collections.Generic;
@@ -21,7 +20,7 @@ namespace Paymetheus.Rpc
{
public sealed class WalletClient : IDisposable
{
- private static readonly SemanticVersion RequiredRpcServerVersion = new SemanticVersion(2, 0, 2);
+ private static readonly SemanticVersion RequiredRpcServerVersion = new SemanticVersion(2, 1, 0);
public static void Initialize()
{
@@ -281,7 +280,7 @@ public async Task, Amount>> SelectUnspentOutputs(Accou
return Tuple.Create(outputs, total);
}
- public async Task, Amount, OutputScript>> FundTransactionAsync(
+ public async Task, Amount>> FundTransactionAsync(
Account account, Amount targetAmount, int requiredConfirmations)
{
var client = WalletService.NewClient(_channel);
@@ -291,17 +290,12 @@ public async Task, Amount, OutputScript>> FundTransact
TargetAmount = targetAmount,
RequiredConfirmations = requiredConfirmations,
IncludeImmatureCoinbases = false,
- IncludeChangeScript = true,
+ IncludeChangeScript = false,
};
var response = await client.FundTransactionAsync(request, cancellationToken: _tokenSource.Token);
var outputs = response.SelectedOutputs.Select(MarshalUnspentOutput).ToList();
var total = (Amount)response.TotalAmount;
- var changeScript = (OutputScript)null;
- if (response.ChangePkScript?.Length != 0)
- {
- changeScript = OutputScript.ParseScript(response.ChangePkScript.ToByteArray());
- }
- return Tuple.Create(outputs, total, changeScript);
+ return Tuple.Create(outputs, total);
}
public async Task> SignTransactionAsync(string passphrase, Transaction tx)
diff --git a/Paymetheus.Rpc/protos/api.proto b/Paymetheus.Rpc/protos/api.proto
index 4c17cc0..571dc23 100644
--- a/Paymetheus.Rpc/protos/api.proto
+++ b/Paymetheus.Rpc/protos/api.proto
@@ -212,6 +212,7 @@ message FundTransactionResponse {
bytes pk_script = 4;
int64 receive_time = 5;
bool from_coinbase = 6;
+ int32 tree = 7;
}
repeated PreviousOutput selected_outputs = 1;
int64 total_amount = 2;
diff --git a/Paymetheus/ViewModels/CreateTransactionViewModel.cs b/Paymetheus/ViewModels/CreateTransactionViewModel.cs
index 855937b..3b2c730 100644
--- a/Paymetheus/ViewModels/CreateTransactionViewModel.cs
+++ b/Paymetheus/ViewModels/CreateTransactionViewModel.cs
@@ -34,6 +34,9 @@ public CreateTransactionViewModel() : base()
AddPendingOutput();
}
+ private Transaction _pendingTransaction;
+ private OutputScript _changeScript;
+
private AccountViewModel _selectedAccount;
public AccountViewModel SelectedAccount
{
@@ -197,15 +200,23 @@ public bool PublishChecked
public ICommand AddPendingOutputCommand { get; }
public ICommand RemovePendingOutputCommand { get; }
- private void AddPendingOutput()
+ private async void AddPendingOutput()
{
var pendingOutput = new PendingOutput();
pendingOutput.Changed += PendingOutput_Changed;
PendingOutputs.Add(pendingOutput);
- RecalculateTransaction();
+
+ try
+ {
+ await RecalculatePendingTransaction();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error");
+ }
}
- private void RemovePendingOutput(PendingOutput item)
+ private async void RemovePendingOutput(PendingOutput item)
{
if (PendingOutputs.Remove(item))
{
@@ -216,34 +227,106 @@ private void RemovePendingOutput(PendingOutput item)
AddPendingOutput();
}
- RecalculateTransaction();
+ try
+ {
+ await RecalculatePendingTransaction();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error");
+ }
}
}
- private void PendingOutput_Changed(object sender, EventArgs e)
+ private async void PendingOutput_Changed(object sender, EventArgs e)
{
- RecalculateTransaction();
+ try
+ {
+ await RecalculatePendingTransaction();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error");
+ }
}
- private void RecalculateTransaction()
+ private async Task RecalculatePendingTransaction()
{
- if (PendingOutputs.Count > 0 && PendingOutputs.All(x => x.IsValid))
+ if (PendingOutputs.Count == 0 || PendingOutputs.Any(x => !x.IsValid))
+ {
+ UnsetPendingTransaction();
+ return;
+ }
+
+ var walletClient = App.Current.Synchronizer?.WalletRpcClient;
+
+ if (_changeScript == null)
+ {
+ var changeAddress = await walletClient.NextInternalAddressAsync(SelectedAccount.Account);
+ _changeScript = Address.Decode(changeAddress).BuildScript();
+ }
+
+ var outputs = PendingOutputs.Select(po =>
+ {
+ var amount = po.OutputAmount;
+ var script = po.BuildOutputScript().Script;
+ return new Transaction.Output(amount, Transaction.Output.LatestPkScriptVersion, script);
+ }).ToArray();
+
+ TransactionAuthor.InputSource inputSource = async targetAmount =>
{
- // TODO: calculate estimated fee
- EstimatedFee = 0;
- EstimatedRemainingBalance = 0;
+ var inputs = new Transaction.Input[0];
+ // TODO: don't hardcode confs
+ var funding = await walletClient.FundTransactionAsync(SelectedAccount.Account, targetAmount, 1);
+ if (funding.Item2 >= targetAmount)
+ {
+ inputs = funding.Item1.Select(o =>
+ Transaction.Input.CreateFromPrefix(new Transaction.OutPoint(o.TransactionHash, o.OutputIndex, o.Tree),
+ TransactionRules.MaxInputSequence)).ToArray();
+ }
+ return Tuple.Create(funding.Item2, inputs);
+ };
- // TODO: only make executable if we know the transaction can be created
- FinishCreateTransaction.Executable = true;
+ try
+ {
+ var r = await TransactionAuthor.BuildUnsignedTransaction(outputs, _changeScript,
+ TransactionFees.DefaultFeePerKb, inputSource);
+ SetPendingTransaction(r.Item1, r.Item2, outputs.Sum(o => o.Amount));
}
- else
+ catch (Exception ex)
{
- EstimatedFee = null;
- EstimatedRemainingBalance = null;
- FinishCreateTransaction.Executable = false;
+ UnsetPendingTransaction();
+
+ // Insufficient funds will need a nicer error displayed somehow. For now, hide it
+ // while disabling the UI to create the transaction. All other errors are unexpected.
+ if (!(ex is InsufficientFundsException)) throw;
}
}
+ private void UnsetPendingTransaction()
+ {
+ _pendingTransaction = null;
+ EstimatedFee = null;
+ EstimatedRemainingBalance = null;
+ FinishCreateTransaction.Executable = false;
+ }
+
+ private void SetPendingTransaction(Transaction unsignedTransaction, Amount inputAmount, Amount targetOutput)
+ {
+ var wallet = App.Current.Synchronizer.Wallet;
+ if (wallet == null)
+ return;
+
+ var actualFee = TransactionFees.ActualFee(unsignedTransaction, inputAmount);
+ var totalAccountBalance = wallet.LookupAccountProperties(SelectedAccount.Account).TotalBalance;
+
+ _pendingTransaction = unsignedTransaction;
+
+ EstimatedFee = actualFee;
+ EstimatedRemainingBalance = totalAccountBalance - targetOutput - actualFee;
+ FinishCreateTransaction.Executable = true;
+ }
+
private void FinishCreateTransactionAction()
{
try
@@ -273,55 +356,16 @@ private void SignTransaction(bool publish)
var shell = ViewModelLocator.ShellViewModel as ShellViewModel;
if (shell != null)
{
- Func action = (passphrase) => SignTransactionWithPassphrase(passphrase, outputs, publish);
+ Func action =
+ passphrase => SignTransactionWithPassphrase(passphrase, _pendingTransaction, publish);
shell.VisibleDialogContent = new PassphraseDialogViewModel(shell, "Enter passphrase to sign transaction", "Sign", action);
}
}
- private async Task SignTransactionWithPassphrase(string passphrase, Transaction.Output[] outputs, bool publishImmediately)
+ private async Task SignTransactionWithPassphrase(string passphrase, Transaction tx, bool publishImmediately)
{
var walletClient = App.Current.Synchronizer.WalletRpcClient;
- var requiredConfirmations = 1; // TODO: Don't hardcode confs.
- var targetAmount = outputs.Sum(o => o.Amount);
- var targetFee = (Amount)1e6; // TODO: Don't hardcode fee/kB.
- var funding = await walletClient.FundTransactionAsync(SelectedAccount.Account, targetAmount + targetFee, requiredConfirmations);
- var fundingAmount = funding.Item2;
- if (fundingAmount < targetAmount + targetFee)
- {
- MessageBox.Show($"Transaction requires {(Amount)(targetAmount + targetFee)} input value but only {fundingAmount} is spendable.",
- "Insufficient funds to create transaction.");
- return;
- }
-
- var selectedOutputs = funding.Item1;
- var inputs = selectedOutputs
- .Select(o =>
- {
- var prevOutPoint = new Transaction.OutPoint(o.TransactionHash, o.OutputIndex, 0);
- return Transaction.Input.CreateFromPrefix(prevOutPoint, TransactionRules.MaxInputSequence);
- })
- .ToArray();
-
- // TODO: Port the fee estimation logic from btcwallet. Using a hardcoded fee is unacceptable.
- var estimatedFee = targetFee;
-
- var changePkScript = funding.Item3;
- if (changePkScript != null)
- {
- // Change output amount is calculated by solving for changeAmount with the equation:
- // estimatedFee = fundingAmount - (targetAmount + changeAmount)
- var changeOutput = new Transaction.Output(fundingAmount - targetAmount - estimatedFee,
- Transaction.Output.LatestPkScriptVersion, changePkScript.Script);
- var outputsList = outputs.ToList();
- // TODO: Randomize change output position.
- outputsList.Add(changeOutput);
- outputs = outputsList.ToArray();
- }
-
- // TODO: User may want to set the locktime.
- var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0);
-
- var signingResponse = await walletClient.SignTransactionAsync(passphrase, unsignedTransaction);
+ var signingResponse = await walletClient.SignTransactionAsync(passphrase, tx);
var complete = signingResponse.Item2;
if (!complete)
{
@@ -330,18 +374,19 @@ private async Task SignTransactionWithPassphrase(string passphrase, Transaction.
}
var signedTransaction = signingResponse.Item1;
- MessageBox.Show($"Created tx with {estimatedFee} fee.");
-
if (!publishImmediately)
{
MessageBox.Show("Reviewing signed transaction before publishing is not implemented yet.");
return;
}
- // TODO: The client just deserialized the transaction, so serializing it is a
- // little silly. This could be optimized.
await walletClient.PublishTransactionAsync(signedTransaction.Serialize());
MessageBox.Show("Published transaction.");
+
+ _pendingTransaction = null;
+ _changeScript = null;
+ PendingOutputs.Clear();
+ AddPendingOutput();
}
}
}