mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
(feat) monero settlement thresholds (#5807)
* (bug) treat xmr wallet directory as required The wallet directory configuration setting is required because the `UIMoneroLikeStoreController`'s `GetMoneroLikePaymentMethodViewModel` method checks if the wallet file exists, and to do that in needs the directory. * (feat) xmr settlement thresholds Adds the ability to select zero, 1, 10, or a custom number of confirmations as the payment settlement threshold. * (review) fix validation message not showing --------- Co-authored-by: Henry Hollingworth <henry.hollingworth@alcoa.com>
This commit is contained in:
parent
0e64df3bbf
commit
c56c6401d6
@ -73,7 +73,7 @@ namespace BTCPayServer.Services.Altcoins.Monero
|
||||
var daemonPassword =
|
||||
configuration.GetOrDefault<string>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_password", null);
|
||||
if (daemonUri == null || walletDaemonUri == null)
|
||||
if (daemonUri == null || walletDaemonUri == null || walletDaemonWalletDirectory == null)
|
||||
{
|
||||
throw new ConfigException($"{moneroLikeSpecificBtcPayNetwork.CryptoCode} is misconfigured");
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
public long AddressIndex { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
public decimal NextNetworkFee { get; set; }
|
||||
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -16,6 +16,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
public long BlockHeight { get; set; }
|
||||
public long ConfirmationCount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||
|
||||
public BTCPayNetworkBase Network { get; set; }
|
||||
public long LockTime { get; set; } = 0;
|
||||
@ -48,6 +49,12 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (InvoiceSettledConfirmationThreshold.HasValue)
|
||||
{
|
||||
return ConfirmationCount >= InvoiceSettledConfirmationThreshold;
|
||||
}
|
||||
|
||||
switch (speedPolicy)
|
||||
{
|
||||
case SpeedPolicy.HighSpeed:
|
||||
|
@ -59,6 +59,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
AccountIndex = supportedPaymentMethod.AccountIndex,
|
||||
AddressIndex = address.AddressIndex,
|
||||
DepositAddress = address.Address,
|
||||
InvoiceSettledConfirmationThreshold = supportedPaymentMethod.InvoiceSettledConfirmationThreshold,
|
||||
Activated = true
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||
[JsonIgnore]
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, MoneroPaymentType.Instance);
|
||||
}
|
||||
|
@ -324,6 +324,11 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
string txId, long confirmations, long blockHeight, long locktime, InvoiceEntity invoice,
|
||||
BlockingCollection<(PaymentEntity Payment, InvoiceEntity invoice)> paymentsToUpdate)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork(cryptoCode);
|
||||
var moneroPaymentMethodDetails = invoice
|
||||
.GetPaymentMethod(network, MoneroPaymentType.Instance)
|
||||
.GetPaymentMethodDetails() as MoneroLikeOnChainPaymentMethodDetails;
|
||||
|
||||
//construct the payment data
|
||||
var paymentData = new MoneroLikePaymentData()
|
||||
{
|
||||
@ -335,7 +340,8 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
Amount = totalAmount,
|
||||
BlockHeight = blockHeight,
|
||||
Network = _networkProvider.GetNetwork(cryptoCode),
|
||||
LockTime = locktime
|
||||
LockTime = locktime,
|
||||
InvoiceSettledConfirmationThreshold = moneroPaymentMethodDetails.InvoiceSettledConfirmationThreshold
|
||||
};
|
||||
|
||||
//check if this tx exists as a payment to this invoice already
|
||||
|
@ -104,6 +104,14 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
new SelectListItem(
|
||||
$"{account.AccountIndex} - {(string.IsNullOrEmpty(account.Label) ? "No label" : account.Label)}",
|
||||
account.AccountIndex.ToString(CultureInfo.InvariantCulture)));
|
||||
var settlementThresholdChoice = settings.InvoiceSettledConfirmationThreshold switch
|
||||
{
|
||||
null => MoneroLikeSettlementThresholdChoice.StoreSpeedPolicy,
|
||||
0 => MoneroLikeSettlementThresholdChoice.ZeroConfirmation,
|
||||
1 => MoneroLikeSettlementThresholdChoice.AtLeastOne,
|
||||
10 => MoneroLikeSettlementThresholdChoice.AtLeastTen,
|
||||
_ => MoneroLikeSettlementThresholdChoice.Custom
|
||||
};
|
||||
return new MoneroLikePaymentMethodViewModel()
|
||||
{
|
||||
WalletFileFound = System.IO.File.Exists(fileAddress),
|
||||
@ -114,7 +122,11 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
CryptoCode = cryptoCode,
|
||||
AccountIndex = settings?.AccountIndex ?? accountsResponse?.SubaddressAccounts?.FirstOrDefault()?.AccountIndex ?? 0,
|
||||
Accounts = accounts == null ? null : new SelectList(accounts, nameof(SelectListItem.Value),
|
||||
nameof(SelectListItem.Text))
|
||||
nameof(SelectListItem.Text)),
|
||||
SettlementConfirmationThresholdChoice = settlementThresholdChoice,
|
||||
CustomSettlementConfirmationThreshold = settlementThresholdChoice is MoneroLikeSettlementThresholdChoice.Custom
|
||||
? settings.InvoiceSettledConfirmationThreshold
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
@ -250,6 +262,8 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
vm.Enabled = viewModel.Enabled;
|
||||
vm.NewAccountLabel = viewModel.NewAccountLabel;
|
||||
vm.AccountIndex = viewModel.AccountIndex;
|
||||
vm.SettlementConfirmationThresholdChoice = viewModel.SettlementConfirmationThresholdChoice;
|
||||
vm.CustomSettlementConfirmationThreshold = viewModel.CustomSettlementConfirmationThreshold;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -258,7 +272,15 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
storeData.SetSupportedPaymentMethod(new MoneroSupportedPaymentMethod()
|
||||
{
|
||||
AccountIndex = viewModel.AccountIndex,
|
||||
CryptoCode = viewModel.CryptoCode
|
||||
CryptoCode = viewModel.CryptoCode,
|
||||
InvoiceSettledConfirmationThreshold = viewModel.SettlementConfirmationThresholdChoice switch
|
||||
{
|
||||
MoneroLikeSettlementThresholdChoice.ZeroConfirmation => 0,
|
||||
MoneroLikeSettlementThresholdChoice.AtLeastOne => 1,
|
||||
MoneroLikeSettlementThresholdChoice.AtLeastTen => 10,
|
||||
MoneroLikeSettlementThresholdChoice.Custom when viewModel.CustomSettlementConfirmationThreshold is { } custom => custom,
|
||||
_ => null
|
||||
}
|
||||
});
|
||||
|
||||
blob.SetExcluded(new PaymentMethodId(viewModel.CryptoCode, MoneroPaymentType.Instance), !viewModel.Enabled);
|
||||
@ -297,7 +319,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
public IEnumerable<MoneroLikePaymentMethodViewModel> Items { get; set; }
|
||||
}
|
||||
|
||||
public class MoneroLikePaymentMethodViewModel
|
||||
public class MoneroLikePaymentMethodViewModel : IValidatableObject
|
||||
{
|
||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
@ -309,8 +331,39 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
public bool WalletFileFound { get; set; }
|
||||
[Display(Name = "View-Only Wallet File")]
|
||||
public IFormFile WalletFile { get; set; }
|
||||
[Display(Name = "Wallet Keys File")]
|
||||
public IFormFile WalletKeysFile { get; set; }
|
||||
[Display(Name = "Wallet Password")]
|
||||
public string WalletPassword { get; set; }
|
||||
[Display(Name = "Consider the invoice settled when the payment transaction …")]
|
||||
public MoneroLikeSettlementThresholdChoice SettlementConfirmationThresholdChoice { get; set; }
|
||||
[Display(Name = "Required Confirmations"), Range(0, 100)]
|
||||
public long? CustomSettlementConfirmationThreshold { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (SettlementConfirmationThresholdChoice is MoneroLikeSettlementThresholdChoice.Custom
|
||||
&& CustomSettlementConfirmationThreshold is null)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
"You must specify the number of required confirmations when using a custom threshold.",
|
||||
new[] { nameof(CustomSettlementConfirmationThreshold) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MoneroLikeSettlementThresholdChoice
|
||||
{
|
||||
[Display(Name = "Store Speed Policy", Description = "Use the store's speed policy")]
|
||||
StoreSpeedPolicy,
|
||||
[Display(Name = "Zero Confirmation", Description = "Is unconfirmed")]
|
||||
ZeroConfirmation,
|
||||
[Display(Name = "At Least One", Description = "Has at least 1 confirmation")]
|
||||
AtLeastOne,
|
||||
[Display(Name = "At Least Ten", Description = "Has at least 10 confirmations")]
|
||||
AtLeastTen,
|
||||
[Display(Name = "Custom", Description = "Custom")]
|
||||
Custom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Services.Altcoins.Monero.UI.UIMoneroLikeStoreController.MoneroLikePaymentMethodViewModel
|
||||
@using MoneroLikePaymentMethodViewModel = BTCPayServer.Services.Altcoins.Monero.UI.UIMoneroLikeStoreController.MoneroLikePaymentMethodViewModel
|
||||
@using MoneroLikeSettlementThresholdChoice = BTCPayServer.Services.Altcoins.Monero.UI.UIMoneroLikeStoreController.MoneroLikeSettlementThresholdChoice;
|
||||
@model MoneroLikePaymentMethodViewModel
|
||||
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
@ -10,7 +12,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div asp-validation-summary="All"></div>
|
||||
@if (Model.Summary != null)
|
||||
{
|
||||
<div class="card">
|
||||
@ -92,6 +94,40 @@
|
||||
<span asp-validation-for="Enabled" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="SettlementConfirmationThresholdChoice" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener" title="More information...">
|
||||
<vc:icon symbol="info" />
|
||||
</a>
|
||||
<select
|
||||
asp-for="SettlementConfirmationThresholdChoice"
|
||||
asp-items="Html.GetEnumSelectList<MoneroLikeSettlementThresholdChoice>()"
|
||||
class="form-select w-auto"
|
||||
onchange="
|
||||
document.getElementById('unconfirmed-warning').hidden = this.value !== '@((int)MoneroLikeSettlementThresholdChoice.ZeroConfirmation)';
|
||||
document.getElementById('custom-confirmation-value').hidden = this.value !== '@((int)MoneroLikeSettlementThresholdChoice.Custom)';">
|
||||
</select>
|
||||
<span asp-validation-for="SettlementConfirmationThresholdChoice" class="text-danger"></span>
|
||||
<p class="info-note my-3 text-warning" id="unconfirmed-warning" role="alert" hidden="@(Model.SettlementConfirmationThresholdChoice is not MoneroLikeSettlementThresholdChoice.ZeroConfirmation)">
|
||||
<vc:icon symbol="warning" />
|
||||
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="custom-confirmation-value" hidden="@(Model.SettlementConfirmationThresholdChoice is not MoneroLikeSettlementThresholdChoice.Custom)">
|
||||
<label asp-for="CustomSettlementConfirmationThreshold" class="form-label"></label>
|
||||
<input
|
||||
asp-for="CustomSettlementConfirmationThreshold"
|
||||
type="number"
|
||||
value="@(Model.CustomSettlementConfirmationThreshold)"
|
||||
class="form-control w-auto"
|
||||
min="0"
|
||||
max="100"
|
||||
pattern="\d+"
|
||||
/>
|
||||
<span asp-validation-for="CustomSettlementConfirmationThreshold" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" id="SaveButton">Save</button>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user