diff --git a/BTCPayServer.Client/BTCPayServer.Client.csproj b/BTCPayServer.Client/BTCPayServer.Client.csproj
index 3f2894cb0..51de680e0 100644
--- a/BTCPayServer.Client/BTCPayServer.Client.csproj
+++ b/BTCPayServer.Client/BTCPayServer.Client.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/BTCPayServer.Common/BTCPayServer.Common.csproj b/BTCPayServer.Common/BTCPayServer.Common.csproj
index 6d4db4535..1908b7d92 100644
--- a/BTCPayServer.Common/BTCPayServer.Common.csproj
+++ b/BTCPayServer.Common/BTCPayServer.Common.csproj
@@ -4,6 +4,6 @@
-
+
diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml
index 5121996de..6c23f719c 100644
--- a/BTCPayServer.Tests/docker-compose.yml
+++ b/BTCPayServer.Tests/docker-compose.yml
@@ -78,7 +78,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
- image: nicolasdorier/nbxplorer:2.1.25
+ image: nicolasdorier/nbxplorer:2.1.26
restart: unless-stopped
ports:
- "32838:32838"
diff --git a/BTCPayServer/Controllers/WalletsController.PSBT.cs b/BTCPayServer/Controllers/WalletsController.PSBT.cs
index f855e9895..f7ed33e3b 100644
--- a/BTCPayServer/Controllers/WalletsController.PSBT.cs
+++ b/BTCPayServer/Controllers/WalletsController.PSBT.cs
@@ -44,7 +44,11 @@ namespace BTCPayServer.Controllers
}
psbtRequest.FeePreference = new FeePreference();
- psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(sendModel.FeeSatoshiPerByte), 1);
+ if (sendModel.FeeSatoshiPerByte is decimal v &&
+ v > decimal.Zero)
+ {
+ psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(v), 1);
+ }
if (sendModel.NoChange)
{
psbtRequest.ExplicitChangeAddress = psbtRequest.Destinations.First().Destination;
diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs
index ce9d0a42a..3d9b56098 100644
--- a/BTCPayServer/Controllers/WalletsController.cs
+++ b/BTCPayServer/Controllers/WalletsController.cs
@@ -420,14 +420,35 @@ namespace BTCPayServer.Controllers
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
- var recommendedFees = feeProvider.GetFeeRateAsync();
+ var recommendedFees = new[] {
+ TimeSpan.FromMinutes(10.0),
+ TimeSpan.FromMinutes(60.0),
+ TimeSpan.FromHours(6.0),
+ TimeSpan.FromHours(24.0),
+ }
+ .Select(time => network.NBitcoinNetwork.Consensus.GetExpectedBlocksFor(time))
+ .Select(blockCount => feeProvider.GetFeeRateAsync((int)blockCount))
+ .ToArray();
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
model.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
.GetMetadataAsync(GetDerivationSchemeSettings(walletId).AccountDerivation,
WellknownMetadataKeys.MasterHDKey));
model.CurrentBalance = await balance;
- model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
- model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
+ model.RecommendedSatoshiPerByte = new decimal?[recommendedFees.Length];
+ for (int i = 0; i < model.RecommendedSatoshiPerByte.Length; i++)
+ {
+ decimal? feeRate = null;
+ try
+ {
+ feeRate = (await recommendedFees[i]).SatoshiPerByte;
+ }
+ catch
+ {
+
+ }
+ model.RecommendedSatoshiPerByte[i] = feeRate;
+ }
+ model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte.Reverse().Where(r => r is decimal).FirstOrDefault();
model.SupportRBF = network.SupportRBF;
using (CancellationTokenSource cts = new CancellationTokenSource())
{
@@ -589,6 +610,25 @@ namespace BTCPayServer.Controllers
"You are sending more than what you own", this);
}
}
+ if (vm.FeeSatoshiPerByte is decimal fee)
+ {
+ if (fee < 0)
+ {
+ vm.AddModelError(model => model.FeeSatoshiPerByte,
+ "The fee rate should be above 0", this);
+ }
+ if (fee > 5_000m)
+ {
+ vm.AddModelError(model => model.FeeSatoshiPerByte,
+ "The fee rate is absurdly high", this);
+ }
+ if (_dashboard.Get(network.CryptoCode).Status?.BitcoinStatus?.MinRelayTxFee?.SatoshiPerByte is decimal minFee)
+ {
+ if (vm.FeeSatoshiPerByte < minFee)
+ vm.AddModelError(model => model.FeeSatoshiPerByte,
+ $"The fee rate is lower than the minimum relay fee ({vm.FeeSatoshiPerByte} < {minFee})", this);
+ }
+ }
if (!ModelState.IsValid)
return View(vm);
diff --git a/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs b/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs
index d7fd9b4f6..ec514a300 100644
--- a/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs
+++ b/BTCPayServer/Models/WalletViewModels/WalletSendModel.cs
@@ -26,12 +26,18 @@ namespace BTCPayServer.Models.WalletViewModels
public string CryptoCode { get; set; }
- public int RecommendedSatoshiPerByte { get; set; }
+ public string[] RecommendedSatoshiLabels = new string[]
+ {
+ "10 minutes",
+ "1 hour",
+ "6 hours",
+ "1 day"
+ };
+ public decimal?[] RecommendedSatoshiPerByte { get; set; }
- [Range(1, int.MaxValue)]
[Display(Name = "Fee rate (satoshi per byte)")]
[Required]
- public int FeeSatoshiPerByte { get; set; }
+ public decimal? FeeSatoshiPerByte { get; set; }
[Display(Name = "Make sure no change UTXO is created")]
public bool NoChange { get; set; }
diff --git a/BTCPayServer/Views/Wallets/WalletSend.cshtml b/BTCPayServer/Views/Wallets/WalletSend.cshtml
index 0529b0658..faf1d6c87 100644
--- a/BTCPayServer/Views/Wallets/WalletSend.cshtml
+++ b/BTCPayServer/Views/Wallets/WalletSend.cshtml
@@ -25,7 +25,10 @@
-
+ @for (int i = 0; i < Model.RecommendedSatoshiPerByte.Length; i++)
+ {
+
+ }
@@ -38,7 +41,7 @@
}
}
-
+
@if (Model.InputSelection)
{
@@ -50,10 +53,10 @@
}
-
+
}
-
+
@if (Model.Outputs.Count == 1)
{
@@ -130,10 +133,16 @@
+
- The recommended value is
- satoshi per byte.
+ Target confirmation in:
+ @for (int i = 0; i < Model.RecommendedSatoshiPerByte.Length; i++)
+ {
+ if (Model.RecommendedSatoshiPerByte[i] is null)
+ continue;
+ .
+ }