Introduce FeeStrategy
MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -817,7 +817,7 @@ namespace WalletWasabi.Tests
}
var scp = new Key().ScriptPubKey;
- var res2 = wallet.BuildTransaction(password, new PaymentIntent(new DestinationRequest(scp, Money.Coins(0.05m), label: "foo")), feeTarget: 5, allowUnconfirmed: false);
+ var res2 = wallet.BuildTransaction(password, new PaymentIntent(scp, Money.Coins(0.05m), label: "foo"), FeeStrategy.CreateFromConfirmationTarget(5), allowUnconfirmed: false);
Assert.NotNull(res2.Transaction);
Assert.Single(res2.OuterWalletOutputs);
@@ -838,7 +838,7 @@ namespace WalletWasabi.Tests
Script receive = wallet.GetReceiveKey("Basic").P2wpkhScript;
Money amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 2;
- var res = wallet.BuildTransaction(password, new PaymentIntent(new DestinationRequest(receive, amountToSend, label: "foo")), Constants.SevenDaysConfirmationTarget, allowUnconfirmed: true);
+ var res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
foreach (SmartCoin coin in res.SpentCoins)
{
@@ -886,7 +886,7 @@ namespace WalletWasabi.Tests
receive = wallet.GetReceiveKey("SubtractFeeFromAmount").P2wpkhScript;
amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3;
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, subtractFee: true, label: "foo"), Constants.SevenDaysConfirmationTarget, allowUnconfirmed: true);
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, subtractFee: true, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Equal(2, res.InnerWalletOutputs.Count());
Assert.Empty(res.OuterWalletOutputs);
@@ -919,7 +919,7 @@ namespace WalletWasabi.Tests
#region LowFee
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), Constants.SevenDaysConfirmationTarget, allowUnconfirmed: true);
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Equal(2, res.InnerWalletOutputs.Count());
Assert.Empty(res.OuterWalletOutputs);
@@ -952,7 +952,7 @@ namespace WalletWasabi.Tests
#region MediumFee
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), Constants.OneDayConfirmationTarget, allowUnconfirmed: true);
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.OneDayConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Equal(2, res.InnerWalletOutputs.Count());
Assert.Empty(res.OuterWalletOutputs);
@@ -985,7 +985,7 @@ namespace WalletWasabi.Tests
#region HighFee
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), 2, allowUnconfirmed: true);
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Equal(2, res.InnerWalletOutputs.Count());
Assert.Empty(res.OuterWalletOutputs);
@@ -1025,7 +1025,7 @@ namespace WalletWasabi.Tests
receive = wallet.GetReceiveKey("MaxAmount").P2wpkhScript;
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), Constants.SevenDaysConfirmationTarget, allowUnconfirmed: true);
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Single(res.InnerWalletOutputs);
Assert.Empty(res.OuterWalletOutputs);
@@ -1048,7 +1048,7 @@ namespace WalletWasabi.Tests
var inputCountBefore = res.SpentCoins.Count();
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), Constants.SevenDaysConfirmationTarget,
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy,
allowUnconfirmed: true,
allowedInputs: wallet.Coins.Where(x => !x.Unavailable).Select(x => new TxoRef(x.TransactionId, x.Index)).Take(1));
@@ -1068,7 +1068,7 @@ namespace WalletWasabi.Tests
Assert.Single(res.Transaction.Transaction.Outputs);
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), Constants.SevenDaysConfirmationTarget,
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy,
allowUnconfirmed: true,
allowedInputs: new[] { res.SpentCoins.Select(x => new TxoRef(x.TransactionId, x.Index)).First() });
@@ -1084,8 +1084,7 @@ namespace WalletWasabi.Tests
#region Labeling
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "my label"), Constants.SevenDaysConfirmationTarget,
- allowUnconfirmed: true);
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "my label"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Single(res.InnerWalletOutputs);
Assert.Equal("my label", res.InnerWalletOutputs.Single().Label);
@@ -1096,7 +1095,7 @@ namespace WalletWasabi.Tests
new PaymentIntent(
new DestinationRequest(new Key(), amountToSend, label: "outgoing"),
new DestinationRequest(new Key(), amountToSend, label: "outgoing2")),
- Constants.SevenDaysConfirmationTarget,
+ FeeStrategy.SevenDaysConfirmationTargetStrategy,
allowUnconfirmed: true);
Assert.Single(res.InnerWalletOutputs);
@@ -1129,8 +1128,7 @@ namespace WalletWasabi.Tests
// covers:
// disallow unconfirmed with allowed inputs
- // feeTarget < 2 // NOTE: need to correct alowing 0 and 1
- res = wallet.BuildTransaction(password, toSend, 0, null, false, allowedInputs: allowedInputs);
+ res = wallet.BuildTransaction(password, toSend, FeeStrategy.TwoHoursConfirmationTargetStrategy, false, allowedInputs: allowedInputs);
activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
Assert.Single(res.InnerWalletOutputs);
@@ -1167,7 +1165,7 @@ namespace WalletWasabi.Tests
new PaymentIntent(
new DestinationRequest(k1, MoneyRequest.CreateChange()),
new DestinationRequest(k2, Money.Coins(0.0003m), label: "outgoing")),
- 2);
+ FeeStrategy.TwoHoursConfirmationTargetStrategy);
Assert.Contains(k1.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey));
Assert.Contains(k2.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey));
@@ -1179,7 +1177,7 @@ namespace WalletWasabi.Tests
res = wallet.BuildTransaction(
password,
new PaymentIntent(new Key(), Money.Coins(0.0003m), label: "outgoing"),
- 2);
+ FeeStrategy.TwoHoursConfirmationTargetStrategy);
Assert.True(res.FeePercentOfSent > 1);
@@ -1189,7 +1187,7 @@ namespace WalletWasabi.Tests
new PaymentIntent(
new DestinationRequest(newChangeK.P2wpkhScript, MoneyRequest.CreateChange(), "boo"),
new DestinationRequest(new Key(), Money.Coins(0.0003m), label: "outgoing")),
- 2);
+ FeeStrategy.TwoHoursConfirmationTargetStrategy);
Assert.True(res.FeePercentOfSent > 1);
Assert.Single(res.OuterWalletOutputs);
@@ -1264,24 +1262,23 @@ namespace WalletWasabi.Tests
new DestinationRequest(scp, Money.Satoshis(5))));
Logger.TurnOff();
- // toSend cannot be null
- Assert.Throws<ArgumentNullException>(() => wallet.BuildTransaction(null, null, 0));
+ Assert.Throws<ArgumentNullException>(() => wallet.BuildTransaction(null, null, FeeStrategy.CreateFromConfirmationTarget(4)));
// toSend cannot have a null element
- Assert.Throws<ArgumentNullException>(() => wallet.BuildTransaction(null, new PaymentIntent(new[] { (DestinationRequest)null }), 0));
+ Assert.Throws<ArgumentNullException>(() => wallet.BuildTransaction(null, new PaymentIntent(new[] { (DestinationRequest)null }), FeeStrategy.CreateFromConfirmationTarget(0)));
// toSend cannot have a zero element
- Assert.Throws<ArgumentException>(() => wallet.BuildTransaction(null, new PaymentIntent(new DestinationRequest[] { }), 0));
+ Assert.Throws<ArgumentException>(() => wallet.BuildTransaction(null, new PaymentIntent(new DestinationRequest[] { }), FeeStrategy.SevenDaysConfirmationTargetStrategy));
// feeTarget has to be in the range 0 to 1008
- Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, validIntent, -10));
- Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, validIntent, 2000));
+ Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, validIntent, FeeStrategy.CreateFromConfirmationTarget(-10)));
+ Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, validIntent, FeeStrategy.CreateFromConfirmationTarget(2000)));
// toSend amount sum has to be in range 0 to 2099999997690000
- Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, invalidIntent, 2));
+ Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, invalidIntent, FeeStrategy.TwoHoursConfirmationTargetStrategy));
// toSend negative sum amount
- Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, new PaymentIntent(scp, Money.Satoshis(-10000)), 2));
+ Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, new PaymentIntent(scp, Money.Satoshis(-10000)), FeeStrategy.TwoHoursConfirmationTargetStrategy));
// toSend negative operation amount
Assert.Throws<ArgumentOutOfRangeException>(() => wallet.BuildTransaction(
@@ -1289,10 +1286,10 @@ namespace WalletWasabi.Tests
new PaymentIntent(
new DestinationRequest(scp, Money.Satoshis(20000)),
new DestinationRequest(scp, Money.Satoshis(-10000))),
- 2));
+ FeeStrategy.TwoHoursConfirmationTargetStrategy));
// allowedInputs cannot be empty
- Assert.Throws<ArgumentException>(() => wallet.BuildTransaction(null, validIntent, feeTarget: 2, allowedInputs: new TxoRef[0]));
+ Assert.Throws<ArgumentException>(() => wallet.BuildTransaction(null, validIntent, FeeStrategy.CreateFromConfirmationTarget(2), allowedInputs: new TxoRef[0]));
// "Only one element can contain the AllRemaining flag.
Assert.Throws<ArgumentException>(() => wallet.BuildTransaction(
@@ -1300,8 +1297,7 @@ namespace WalletWasabi.Tests
new PaymentIntent(
new DestinationRequest(scp, MoneyRequest.CreateAllRemaining(), "zero"),
new DestinationRequest(scp, MoneyRequest.CreateAllRemaining(), "zero")),
- Constants.SevenDaysConfirmationTarget,
- null,
+ FeeStrategy.SevenDaysConfirmationTargetStrategy,
false));
// Get some money, make it confirm.
@@ -1331,24 +1327,24 @@ namespace WalletWasabi.Tests
var operations = new PaymentIntent(
new DestinationRequest(scp, Money.Coins(1m), subtractFee: true),
new DestinationRequest(scp, Money.Coins(0.5m)));
- Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(password, operations, 2, null, false));
+ Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, false));
// No enough money (only one confirmed coin, no unconfirmed allowed)
operations = new PaymentIntent(scp, Money.Coins(1.5m));
- Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, 2));
+ Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy));
// No enough money (only one confirmed coin, unconfirmed allowed)
- Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, 2, null, true));
+ Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, true));
// Add new money with no confirmation
var txId2 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(2m));
await Task.Delay(1000); // Wait tx to arrive and get processed.
// Enough money (one confirmed coin and one unconfirmed coin, unconfirmed are NOT allowed)
- Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, 2, null, false));
+ Assert.Throws<InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, false));
// Enough money (one confirmed coin and one unconfirmed coin, unconfirmed are allowed)
- var btx = wallet.BuildTransaction(password, operations, 2, null, true);
+ var btx = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, true);
Assert.Equal(2, btx.SpentCoins.Count());
Assert.Equal(1, btx.SpentCoins.Count(c => c.Confirmed));
Assert.Equal(1, btx.SpentCoins.Count(c => !c.Confirmed));
@@ -1360,12 +1356,12 @@ namespace WalletWasabi.Tests
new PaymentIntent(
new DestinationRequest(scp, MoneyRequest.CreateAllRemaining()),
new DestinationRequest(scp, MoneyRequest.CreateAllRemaining())),
- 2));
+ FeeStrategy.TwoHoursConfirmationTargetStrategy));
Logger.TurnOn();
operations = new PaymentIntent(scp, Money.Coins(0.5m));
- btx = wallet.BuildTransaction(password, operations, 2);
+ btx = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy);
}
finally
{
@@ -1448,11 +1444,11 @@ namespace WalletWasabi.Tests
// Send money before reorg.
var operations = new PaymentIntent(scp, Money.Coins(0.011m));
- var btx1 = wallet.BuildTransaction(password, operations, 2);
+ var btx1 = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy);
await wallet.SendTransactionAsync(btx1.Transaction);
operations = new PaymentIntent(scp, Money.Coins(0.012m));
- var btx2 = wallet.BuildTransaction(password, operations, 2, allowUnconfirmed: true);
+ var btx2 = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
await wallet.SendTransactionAsync(btx2.Transaction);
// Test synchronization after fork.
@@ -1471,11 +1467,11 @@ namespace WalletWasabi.Tests
// When we invalidate a block, the transactions set in the invalidated block
// are reintroduced when we generate a new block through the rpc call
operations = new PaymentIntent(scp, Money.Coins(0.013m));
- var btx3 = wallet.BuildTransaction(password, operations, 2);
+ var btx3 = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy);
await wallet.SendTransactionAsync(btx3.Transaction);
operations = new PaymentIntent(scp, Money.Coins(0.014m));
- var btx4 = wallet.BuildTransaction(password, operations, 2, allowUnconfirmed: true);
+ var btx4 = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
await wallet.SendTransactionAsync(btx4.Transaction);
// Test synchronization after fork with different transactions.
@@ -1618,12 +1614,12 @@ namespace WalletWasabi.Tests
new DestinationRequest(key.P2wpkhScript, Money.Coins(0.01m)),
new DestinationRequest(new Key().ScriptPubKey, Money.Coins(0.01m)),
new DestinationRequest(new Key().ScriptPubKey, Money.Coins(0.01m)));
- var tx1Res = wallet.BuildTransaction(password, operations, 2, allowUnconfirmed: true);
+ var tx1Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Equal(1, tx1Res.OuterWalletOutputs.Single(x => x.ScriptPubKey == key.P2wpkhScript).AnonymitySet);
// Spend the unconfirmed coin (send it to ourself)
operations = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.5m));
- tx1Res = wallet.BuildTransaction(password, operations, 2, allowUnconfirmed: true);
+ tx1Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
await wallet.SendTransactionAsync(tx1Res.Transaction);
while (wallet.Coins.Count != 3)
@@ -1645,7 +1641,7 @@ namespace WalletWasabi.Tests
// Spend the unconfirmed and unspent coin (send it to ourself)
operations = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.5m), subtractFee: true);
- var tx2Res = wallet.BuildTransaction(password, operations, 2, allowUnconfirmed: true);
+ var tx2Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
await wallet.SendTransactionAsync(tx2Res.Transaction);
while (wallet.Coins.Count != 4)
@@ -1688,7 +1684,7 @@ namespace WalletWasabi.Tests
// Test coin basic count.
var coinCount = wallet.Coins.Count;
var to = wallet.GetReceiveKey("foo");
- var res = wallet.BuildTransaction(password, new PaymentIntent(to.P2wpkhScript, Money.Coins(0.2345m), label: "bar"), 2, allowUnconfirmed: true);
+ var res = wallet.BuildTransaction(password, new PaymentIntent(to.P2wpkhScript, Money.Coins(0.2345m), label: "bar"), FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
await wallet.SendTransactionAsync(res.Transaction);
Assert.Equal(coinCount + 2, wallet.Coins.Count);
Assert.Equal(2, wallet.Coins.Count(x => !x.Confirmed));

MODIFY
WalletWasabi/Helpers/Constants.cs
WalletWasabi/Helpers/Constants.cs
@@ -76,11 +76,12 @@ namespace WalletWasabi.Helpers
}
}
- public const int BigFileReadWriteBufferSize = 1 * 1024 * 1024;
-
+ public const int TwoHoursConfirmationTarget = 2;
public const int OneDayConfirmationTarget = 144;
public const int SevenDaysConfirmationTarget = 1008;
+ public const int BigFileReadWriteBufferSize = 1 * 1024 * 1024;
+
public const int DefaultTorSocksPort = 9050;
public const int DefaultTorBrowserSocksPort = 9150;
public const int DefaultTorControlPort = 9051;

ADD
WalletWasabi/Models/TransactionBuilding/FeeStrategy.cs
+using NBitcoin;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using WalletWasabi.Helpers;
+
+namespace WalletWasabi.Models.TransactionBuilding
+{
+ public class FeeStrategy
+ {
+ private int _target;
+ private FeeRate _rate;
+
+ public FeeStrategyType Type { get; }
+
+ public int Target
+ {
+ get
+ {
+ if (Type != FeeStrategyType.Target)
+ {
+ throw new NotSupportedException($"Cannot get {nameof(Target)} with {nameof(FeeStrategyType)} {Type}.");
+ }
+ return _target;
+ }
+ }
+
+ public FeeRate Rate
+ {
+ get
+ {
+ if (Type != FeeStrategyType.Rate)
+ {
+ throw new NotSupportedException($"Cannot get {nameof(Target)} with {nameof(FeeStrategyType)} {Type}.");
+ }
+ return _rate;
+ }
+ }
+
+ public static FeeStrategy TwoHoursConfirmationTargetStrategy { get; } = CreateFromConfirmationTarget(Constants.TwoHoursConfirmationTarget);
+ public static FeeStrategy OneDayConfirmationTargetStrategy { get; } = CreateFromConfirmationTarget(Constants.OneDayConfirmationTarget);
+ public static FeeStrategy SevenDaysConfirmationTargetStrategy { get; } = CreateFromConfirmationTarget(Constants.SevenDaysConfirmationTarget);
+
+ public static FeeStrategy CreateFromConfirmationTarget(int confirmationTarget) => new FeeStrategy(FeeStrategyType.Target, confirmationTarget: confirmationTarget, feeRate: null);
+
+ public static FeeStrategy CreateFromFeeRate(FeeRate feeRate) => new FeeStrategy(FeeStrategyType.Target, confirmationTarget: null, feeRate: feeRate);
+
+ private FeeStrategy(FeeStrategyType type, int? confirmationTarget, FeeRate feeRate)
+ {
+ Type = type;
+ if (type == FeeStrategyType.Rate)
+ {
+ if (confirmationTarget != null)
+ {
+ throw new ArgumentException($"{nameof(confirmationTarget)} must be null.");
+ }
+ Guard.NotNull(nameof(feeRate), feeRate);
+ if (feeRate < new FeeRate(1m))
+ {
+ throw new ArgumentOutOfRangeException(nameof(feeRate), feeRate, "Vannot be less than 1s/b.");
+ }
+ _rate = feeRate;
+ }
+ else if (type == FeeStrategyType.Target)
+ {
+ if (feeRate != null)
+ {
+ throw new ArgumentException($"{nameof(feeRate)} must be null.");
+ }
+ Guard.NotNull(nameof(confirmationTarget), confirmationTarget);
+ _target = Guard.InRangeAndNotNull(nameof(confirmationTarget), confirmationTarget.Value, Constants.TwoHoursConfirmationTarget, Constants.SevenDaysConfirmationTarget);
+ }
+ else
+ {
+ throw new NotSupportedException(type.ToString());
+ }
+ }
+ }
+}

ADD
WalletWasabi/Models/TransactionBuilding/FeeStrategyType.cs
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WalletWasabi.Models.TransactionBuilding
+{
+ public enum FeeStrategyType
+ {
+ Target,
+ Rate
+ }
+}

RENAME
WalletWasabi/Services/SmartCoinSelector.cs
WalletWasabi/Models/TransactionBuilding/SmartCoinSelector.cs
@@ -7,7 +7,7 @@ using WalletWasabi.Models;
using WalletWasabi.Exceptions;
using WalletWasabi.Helpers;
-namespace WalletWasabi.Services
+namespace WalletWasabi.Models.TransactionBuilding
{
public class SmartCoinSelector : ICoinSelector
{

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -869,43 +869,24 @@ namespace WalletWasabi.Services
/// <param name="allowUnconfirmed">Allow to spend unconfirmed transactions, if necessary.</param>
/// <param name="allowedInputs">Only these inputs allowed to be used to build the transaction. The wallet must know the corresponding private keys.</param>
- /// <param name="feeTarget">The target number of blocks to estimate the fee. Null if feeRate is being used instead.</param>
- /// <param name="feeRate">The fee rate for the transaction. Null if feeTarget is being used instead.</param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public BuildTransactionResult BuildTransaction(string password,
PaymentIntent payments,
- int? feeTarget = null,
- FeeRate feeRate = null,
+ FeeStrategy feeStrategy,
bool allowUnconfirmed = false,
IEnumerable<TxoRef> allowedInputs = null)
{
password = password ?? ""; // Correction.
payments = Guard.NotNull(nameof(payments), payments);
- if ((feeRate is null && feeTarget is null)
- || (feeRate != null && feeTarget != null))
- {
- throw new NotSupportedException($"Either {nameof(feeRate)} or {nameof(feeTarget)} must be set and the other should be null.");
- }
-
long totalAmount = payments.TotalAmount.Satoshi;
if (totalAmount < 0 || totalAmount > Constants.MaximumNumberOfSatoshis)
{
throw new ArgumentOutOfRangeException($"{nameof(payments)}.{nameof(payments.TotalAmount)} sum cannot be smaller than 0 or greater than {Constants.MaximumNumberOfSatoshis}.");
}
- if (feeTarget.HasValue)
- {
- Guard.InRangeAndNotNull(nameof(feeTarget), feeTarget.Value, 0, Constants.SevenDaysConfirmationTarget); // Allow 0 and 1, and correct later.
-
- if (feeTarget < 2) // Correct 0 and 1 to 2.
- {
- feeTarget = 2;
- }
- }
-
// Get allowed coins to spend.
List<SmartCoin> allowedSmartCoinInputs; // Inputs that can be used to build the transaction.
if (allowedInputs != null) // If allowedInputs are specified then select the coins from them.
@@ -927,9 +908,19 @@ namespace WalletWasabi.Services
// Get and calculate fee
Logger.LogInfo<WalletService>("Calculating dynamic transaction fee...");
- FeeRate feePerBytes = feeTarget.HasValue ? Synchronizer.GetFeeRate(feeTarget.Value) : feeRate;
- // Sanity check.
- feePerBytes = FeeRate.Max(feePerBytes, new FeeRate(1m));
+ FeeRate feeRate;
+ if (feeStrategy.Type == FeeStrategyType.Target)
+ {
+ feeRate = Synchronizer.GetFeeRate(feeStrategy.Target);
+ }
+ else if (feeStrategy.Type == FeeStrategyType.Rate)
+ {
+ feeRate = feeStrategy.Rate;
+ }
+ else
+ {
+ throw new NotSupportedException(feeStrategy.Type.ToString());
+ }
var smartCoinsByOutpoint = allowedSmartCoinInputs.ToDictionary(s => s.GetOutPoint());
TransactionBuilder builder = Network.CreateTransactionBuilder();
@@ -977,7 +968,7 @@ namespace WalletWasabi.Services
builder.SetChange(changeHdPubKey.P2wpkhScript);
}
- builder.SendEstimatedFees(feePerBytes);
+ builder.SendEstimatedFees(feeRate);
var psbt = builder.BuildPSBT(false);
@@ -1063,7 +1054,7 @@ namespace WalletWasabi.Services
builder.SignPSBT(psbt);
psbt.Finalize();
tx = psbt.ExtractTransaction();
- TransactionPolicyError[] checkResults = builder.Check(tx, feePerBytes);
+ TransactionPolicyError[] checkResults = builder.Check(tx, feeRate);
if (checkResults.Length > 0)
{
throw new InvalidTxException(tx, checkResults);

Introduce FeeStrategy