mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
Make sure wallet support decimal fee, and allow user to select different fee rate based on expected confirmation time
This commit is contained in:
parent
2226884946
commit
87352f0b62
@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.34" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.35" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.10" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.11" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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<string>(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);
|
||||
|
@ -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; }
|
||||
|
@ -25,7 +25,10 @@
|
||||
<input type="hidden" asp-for="Fiat" />
|
||||
<input type="hidden" asp-for="Rate" />
|
||||
<input type="hidden" asp-for="CurrentBalance" />
|
||||
<input type="hidden" asp-for="RecommendedSatoshiPerByte" />
|
||||
@for (int i = 0; i < Model.RecommendedSatoshiPerByte.Length; i++)
|
||||
{
|
||||
<input type="hidden" asp-for="RecommendedSatoshiPerByte[i]" />
|
||||
}
|
||||
<input type="hidden" asp-for="CryptoCode" />
|
||||
<input type="hidden" name="BIP21" id="BIP21" />
|
||||
<ul class="text-danger">
|
||||
@ -38,7 +41,7 @@
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
|
||||
|
||||
@if (Model.InputSelection)
|
||||
{
|
||||
<div class="form-group hide-when-js">
|
||||
@ -50,10 +53,10 @@
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<partial name="CoinSelection"/>
|
||||
<partial name="CoinSelection" />
|
||||
<br>
|
||||
}
|
||||
|
||||
|
||||
@if (Model.Outputs.Count == 1)
|
||||
{
|
||||
<div class="form-group">
|
||||
@ -130,10 +133,16 @@
|
||||
<div class="form-group">
|
||||
<label asp-for="FeeSatoshiPerByte"></label>
|
||||
<input asp-for="FeeSatoshiPerByte" type="number" step="any" class="form-control" />
|
||||
<span asp-validation-for="FeeSatoshiPerByte" class="text-danger"></span>
|
||||
<span id="FeeRate-Error" class="text-danger"></span>
|
||||
<p class="form-text text-muted crypto-info">
|
||||
The recommended value is
|
||||
<button type="button" id="crypto-fee-link" class="btn btn-link p-0 align-baseline">@Model.RecommendedSatoshiPerByte</button> satoshi per byte.
|
||||
Target confirmation in:
|
||||
@for (int i = 0; i < Model.RecommendedSatoshiPerByte.Length; i++)
|
||||
{
|
||||
if (Model.RecommendedSatoshiPerByte[i] is null)
|
||||
continue;
|
||||
<span><button type="button" class="btn btn-link p-0 align-baseline crypto-fee-link" data-feerate="@Model.RecommendedSatoshiPerByte[i]">@Model.RecommendedSatoshiLabels[i]</button>.</span>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
@if (Model.Outputs.Count == 1)
|
||||
@ -146,7 +155,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div class="card">
|
||||
<button id="advancedSettings" class="btn btn-light collapsed" type="button" data-toggle="collapse" data-target="#accordian-advanced" aria-expanded="false" aria-controls="accordian-advanced">
|
||||
Advanced settings
|
||||
@ -174,7 +183,7 @@
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="PayJoinEndpointUrl" class="control-label"></label>
|
||||
<input asp-for="PayJoinEndpointUrl" class="form-control"/>
|
||||
<input asp-for="PayJoinEndpointUrl" class="form-control" />
|
||||
<span asp-validation-for="PayJoinEndpointUrl" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
@ -193,7 +202,8 @@
|
||||
<button name="command" type="submit" class="dropdown-item" value="ledger">... your Ledger Wallet device</button>
|
||||
<button name="command" type="submit" class="dropdown-item" value="seed">... an HD private key or mnemonic seed</button>
|
||||
<button name="command" type="submit" class="dropdown-item" value="analyze-psbt">... a wallet supporting PSBT</button>
|
||||
@if (Model.CryptoCode == "BTC") {
|
||||
@if (Model.CryptoCode == "BTC")
|
||||
{
|
||||
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
||||
}
|
||||
@if (Model.NBXSeedAvailable)
|
||||
@ -202,7 +212,7 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary" >Add another destination</button>
|
||||
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary">Add another destination</button>
|
||||
<button type="button" id="bip21parse" class="ml-1 btn btn-secondary" title="Paste BIP21/Address"><i class="fa fa-paste"></i></button>
|
||||
<button type="button" id="scanqrcode" class="ml-1 btn btn-secondary only-for-js" data-toggle="modal" data-target="#scanModal" title="Scan BIP21/Address with camera"><i class="fa fa-camera"></i></button>
|
||||
</div>
|
||||
|
@ -27,8 +27,8 @@ function updateFiatValueWithCurrentElement() {
|
||||
$(function () {
|
||||
$(".output-amount").on("input", updateFiatValueWithCurrentElement).each(updateFiatValueWithCurrentElement);
|
||||
|
||||
$("#crypto-fee-link").on("click", function (elem) {
|
||||
var val = $(this).text();
|
||||
$(".crypto-fee-link").on("click", function (elem) {
|
||||
var val = $(this).attr("data-feerate").valueOf();
|
||||
$("#FeeSatoshiPerByte").val(val);
|
||||
return false;
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>1.0.4.3</Version>
|
||||
<Version>1.0.4.4</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user