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 =
|
var daemonPassword =
|
||||||
configuration.GetOrDefault<string>(
|
configuration.GetOrDefault<string>(
|
||||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_password", null);
|
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_password", null);
|
||||||
if (daemonUri == null || walletDaemonUri == null)
|
if (daemonUri == null || walletDaemonUri == null || walletDaemonWalletDirectory == null)
|
||||||
{
|
{
|
||||||
throw new ConfigException($"{moneroLikeSpecificBtcPayNetwork.CryptoCode} is misconfigured");
|
throw new ConfigException($"{moneroLikeSpecificBtcPayNetwork.CryptoCode} is misconfigured");
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
|||||||
public long AddressIndex { get; set; }
|
public long AddressIndex { get; set; }
|
||||||
public string DepositAddress { get; set; }
|
public string DepositAddress { get; set; }
|
||||||
public decimal NextNetworkFee { get; set; }
|
public decimal NextNetworkFee { get; set; }
|
||||||
|
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -16,6 +16,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
|||||||
public long BlockHeight { get; set; }
|
public long BlockHeight { get; set; }
|
||||||
public long ConfirmationCount { get; set; }
|
public long ConfirmationCount { get; set; }
|
||||||
public string TransactionId { get; set; }
|
public string TransactionId { get; set; }
|
||||||
|
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||||
|
|
||||||
public BTCPayNetworkBase Network { get; set; }
|
public BTCPayNetworkBase Network { get; set; }
|
||||||
public long LockTime { get; set; } = 0;
|
public long LockTime { get; set; } = 0;
|
||||||
@ -48,6 +49,12 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (InvoiceSettledConfirmationThreshold.HasValue)
|
||||||
|
{
|
||||||
|
return ConfirmationCount >= InvoiceSettledConfirmationThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
switch (speedPolicy)
|
switch (speedPolicy)
|
||||||
{
|
{
|
||||||
case SpeedPolicy.HighSpeed:
|
case SpeedPolicy.HighSpeed:
|
||||||
|
@ -59,6 +59,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
|||||||
AccountIndex = supportedPaymentMethod.AccountIndex,
|
AccountIndex = supportedPaymentMethod.AccountIndex,
|
||||||
AddressIndex = address.AddressIndex,
|
AddressIndex = address.AddressIndex,
|
||||||
DepositAddress = address.Address,
|
DepositAddress = address.Address,
|
||||||
|
InvoiceSettledConfirmationThreshold = supportedPaymentMethod.InvoiceSettledConfirmationThreshold,
|
||||||
Activated = true
|
Activated = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
|||||||
|
|
||||||
public string CryptoCode { get; set; }
|
public string CryptoCode { get; set; }
|
||||||
public long AccountIndex { get; set; }
|
public long AccountIndex { get; set; }
|
||||||
|
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, MoneroPaymentType.Instance);
|
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,
|
string txId, long confirmations, long blockHeight, long locktime, InvoiceEntity invoice,
|
||||||
BlockingCollection<(PaymentEntity Payment, InvoiceEntity invoice)> paymentsToUpdate)
|
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
|
//construct the payment data
|
||||||
var paymentData = new MoneroLikePaymentData()
|
var paymentData = new MoneroLikePaymentData()
|
||||||
{
|
{
|
||||||
@ -335,7 +340,8 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
Amount = totalAmount,
|
Amount = totalAmount,
|
||||||
BlockHeight = blockHeight,
|
BlockHeight = blockHeight,
|
||||||
Network = _networkProvider.GetNetwork(cryptoCode),
|
Network = _networkProvider.GetNetwork(cryptoCode),
|
||||||
LockTime = locktime
|
LockTime = locktime,
|
||||||
|
InvoiceSettledConfirmationThreshold = moneroPaymentMethodDetails.InvoiceSettledConfirmationThreshold
|
||||||
};
|
};
|
||||||
|
|
||||||
//check if this tx exists as a payment to this invoice already
|
//check if this tx exists as a payment to this invoice already
|
||||||
|
@ -104,6 +104,14 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
|||||||
new SelectListItem(
|
new SelectListItem(
|
||||||
$"{account.AccountIndex} - {(string.IsNullOrEmpty(account.Label) ? "No label" : account.Label)}",
|
$"{account.AccountIndex} - {(string.IsNullOrEmpty(account.Label) ? "No label" : account.Label)}",
|
||||||
account.AccountIndex.ToString(CultureInfo.InvariantCulture)));
|
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()
|
return new MoneroLikePaymentMethodViewModel()
|
||||||
{
|
{
|
||||||
WalletFileFound = System.IO.File.Exists(fileAddress),
|
WalletFileFound = System.IO.File.Exists(fileAddress),
|
||||||
@ -114,7 +122,11 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
|||||||
CryptoCode = cryptoCode,
|
CryptoCode = cryptoCode,
|
||||||
AccountIndex = settings?.AccountIndex ?? accountsResponse?.SubaddressAccounts?.FirstOrDefault()?.AccountIndex ?? 0,
|
AccountIndex = settings?.AccountIndex ?? accountsResponse?.SubaddressAccounts?.FirstOrDefault()?.AccountIndex ?? 0,
|
||||||
Accounts = accounts == null ? null : new SelectList(accounts, nameof(SelectListItem.Value),
|
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.Enabled = viewModel.Enabled;
|
||||||
vm.NewAccountLabel = viewModel.NewAccountLabel;
|
vm.NewAccountLabel = viewModel.NewAccountLabel;
|
||||||
vm.AccountIndex = viewModel.AccountIndex;
|
vm.AccountIndex = viewModel.AccountIndex;
|
||||||
|
vm.SettlementConfirmationThresholdChoice = viewModel.SettlementConfirmationThresholdChoice;
|
||||||
|
vm.CustomSettlementConfirmationThreshold = viewModel.CustomSettlementConfirmationThreshold;
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +272,15 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
|||||||
storeData.SetSupportedPaymentMethod(new MoneroSupportedPaymentMethod()
|
storeData.SetSupportedPaymentMethod(new MoneroSupportedPaymentMethod()
|
||||||
{
|
{
|
||||||
AccountIndex = viewModel.AccountIndex,
|
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);
|
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 IEnumerable<MoneroLikePaymentMethodViewModel> Items { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MoneroLikePaymentMethodViewModel
|
public class MoneroLikePaymentMethodViewModel : IValidatableObject
|
||||||
{
|
{
|
||||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||||
public string CryptoCode { get; set; }
|
public string CryptoCode { get; set; }
|
||||||
@ -309,8 +331,39 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
|
|||||||
public bool WalletFileFound { get; set; }
|
public bool WalletFileFound { get; set; }
|
||||||
[Display(Name = "View-Only Wallet File")]
|
[Display(Name = "View-Only Wallet File")]
|
||||||
public IFormFile WalletFile { get; set; }
|
public IFormFile WalletFile { get; set; }
|
||||||
|
[Display(Name = "Wallet Keys File")]
|
||||||
public IFormFile WalletKeysFile { get; set; }
|
public IFormFile WalletKeysFile { get; set; }
|
||||||
|
[Display(Name = "Wallet Password")]
|
||||||
public string WalletPassword { get; set; }
|
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.Views.Stores
|
||||||
@using BTCPayServer.Abstractions.Extensions
|
@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";
|
Layout = "../Shared/_NavLayout.cshtml";
|
||||||
@ -10,7 +12,7 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div asp-validation-summary="All" class="text-danger"></div>
|
<div asp-validation-summary="All"></div>
|
||||||
@if (Model.Summary != null)
|
@if (Model.Summary != null)
|
||||||
{
|
{
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -92,6 +94,40 @@
|
|||||||
<span asp-validation-for="Enabled" class="text-danger"></span>
|
<span asp-validation-for="Enabled" class="text-danger"></span>
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
<button type="submit" class="btn btn-primary" id="SaveButton">Save</button>
|
<button type="submit" class="btn btn-primary" id="SaveButton">Save</button>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user