From 44170d0e0f14f4a43eb8ce8099f5d429e2ca4cf3 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 11 Dec 2024 17:08:41 +0100 Subject: [PATCH] Send: Lightning updates --- .../BTCPayServer/BTCPayPaymentsNotifier.cs | 6 ++--- BTCPayApp.Core/LDK/PaymentsManager.cs | 5 ++-- BTCPayApp.Core/Wallet/OnChainWalletManager.cs | 4 +-- BTCPayApp.UI/Pages/SendPage.razor | 19 ++++++++----- BTCPayApp.UI/StateMiddleware.cs | 2 +- BTCPayApp.UI/Util/RequiredIfAttribute.cs | 10 +++++-- README.md | 27 +++++++++++++++++++ 7 files changed, 54 insertions(+), 19 deletions(-) diff --git a/BTCPayApp.Core/BTCPayServer/BTCPayPaymentsNotifier.cs b/BTCPayApp.Core/BTCPayServer/BTCPayPaymentsNotifier.cs index 74edf6ce..e41bcc46 100644 --- a/BTCPayApp.Core/BTCPayServer/BTCPayPaymentsNotifier.cs +++ b/BTCPayApp.Core/BTCPayServer/BTCPayPaymentsNotifier.cs @@ -37,8 +37,6 @@ await _connectionManager.HubProxy .SendInvoiceUpdate(e.ToInvoice()); } - - public async Task StopAsync(CancellationToken cancellationToken) { _paymentsManager.OnPaymentUpdate -= OnPaymentUpdate; @@ -47,6 +45,6 @@ public async Task StopAsync(CancellationToken cancellationToken) public void StartListen() { _listening = true; - + } -} \ No newline at end of file +} diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index e5298390..55b493ae 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -172,7 +172,6 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentRe amt = Math.Max(amt, explicitAmount?.MilliSatoshi ?? 0); //check if we have a db record with same pay hash but has the preimage set - await using var context = await _dbContextFactory.CreateDbContextAsync(); var inbound = await context.LightningPayments.FirstOrDefaultAsync(lightningPayment => lightningPayment.PaymentHash == paymentRequest.PaymentHash && lightningPayment.Inbound); @@ -180,7 +179,7 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentRe if (inbound is not null) { var successSelfPay = false; - var newOutbound = new AppLightningPayment() + var newOutbound = new AppLightningPayment { Inbound = false, Value = amt, @@ -216,7 +215,7 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentRe return newOutbound; } - var outbound = new AppLightningPayment() + var outbound = new AppLightningPayment { Inbound = false, Value = amt, diff --git a/BTCPayApp.Core/Wallet/OnChainWalletManager.cs b/BTCPayApp.Core/Wallet/OnChainWalletManager.cs index c6f63c4a..a2b3b8c9 100644 --- a/BTCPayApp.Core/Wallet/OnChainWalletManager.cs +++ b/BTCPayApp.Core/Wallet/OnChainWalletManager.cs @@ -63,7 +63,7 @@ private set } public event AsyncEventHandler<(OnChainWalletState Old, OnChainWalletState New)>? StateChanged; - public event AsyncEventHandler? SnapshotUpdated; + public event AsyncEventHandler? OnSnapshotUpdate; public OnChainWalletManager( ConfigProvider configProvider, @@ -368,7 +368,7 @@ private async Task UpdateSnapshot() }).ToArray()) }; await _configProvider.Set(WalletConfig.Key, config, true); - SnapshotUpdated?.Invoke(this, config.CoinSnapshot); + OnSnapshotUpdate?.Invoke(this, config.CoinSnapshot); } finally { diff --git a/BTCPayApp.UI/Pages/SendPage.razor b/BTCPayApp.UI/Pages/SendPage.razor index 8113c888..ce77f412 100644 --- a/BTCPayApp.UI/Pages/SendPage.razor +++ b/BTCPayApp.UI/Pages/SendPage.razor @@ -9,6 +9,8 @@ @using BTCPayApp.UI.Features @using BTCPayApp.UI.Components.Layout @using BTCPayApp.UI.Models +@using BTCPayApp.UI.Pages.SignedOut +@using BTCPayApp.UI.Util @using BTCPayServer.Client @using BTCPayServer.Client.App @using BTCPayServer.Client.App.Models @@ -393,7 +395,7 @@ public string? Destination { get; set; } [Required] public decimal? Amount { get; set; } - [Required] + [RequiredIf(nameof(Address), RequiredIfTargetValue.NotNull)] public decimal? FeeRate { get; set; } public BOLT11PaymentRequest? Bolt11 { get; set; } @@ -413,7 +415,8 @@ ? await LightningNodeManager.IsLightningOurs(lightning) ? SourceState.Supported : SourceState.NotSupported : SourceState.NotConfigured; - OnChainWalletManager.SnapshotUpdated += OnSnapshotUpdated; + OnChainWalletManager.OnSnapshotUpdate += OnTransactionUpdated; + if (PaymentsManager != null) PaymentsManager.OnPaymentUpdate += OnTransactionUpdated; _ = LoadTransactions(); } @@ -422,12 +425,13 @@ { base.DisposeAsyncCore(disposing); - OnChainWalletManager.SnapshotUpdated -= OnSnapshotUpdated; + OnChainWalletManager.OnSnapshotUpdate -= OnTransactionUpdated; + if (PaymentsManager != null) PaymentsManager.OnPaymentUpdate -= OnTransactionUpdated; return ValueTask.CompletedTask; } - private async Task OnSnapshotUpdated(object? sender, CoinSnapshot _) + private async Task OnTransactionUpdated(object? sender, object _) { await LoadTransactions(); } @@ -501,6 +505,7 @@ { Model.Bolt11 = null; Model.Address = null; + Model.FeeRate = null; Model.Transaction = null; return; } @@ -509,6 +514,7 @@ { Model.Bolt11 = BOLT11PaymentRequest.Parse(destination, network); Model.Amount = Model.Bolt11.MinimumAmount.ToDecimal(UnitLightMoney); + return; } catch { @@ -643,12 +649,11 @@ var result = await PaymentsManager.PayInvoice(bolt11, amount); SuccessMessage = $"Payment {result.PaymentId} sent with status {result.Status}"; Model = new SendModel(); - _ = LoadTransactions(); - return new FormResult(true, "Payment sent"); + return new FormResult(true, "Lightning payment sent"); } catch (Exception ex) { - return new FormResult(false, "Payment failed: " + ex.Message); + return new FormResult(false, "Lightning p failed: " + ex.Message); } finally { diff --git a/BTCPayApp.UI/StateMiddleware.cs b/BTCPayApp.UI/StateMiddleware.cs index fa905c91..456b1dd5 100644 --- a/BTCPayApp.UI/StateMiddleware.cs +++ b/BTCPayApp.UI/StateMiddleware.cs @@ -96,7 +96,7 @@ private async Task ListenIn(IDispatcher dispatcher) } }; - onChainWalletManager.SnapshotUpdated += async (sender, args) => + onChainWalletManager.OnSnapshotUpdate += async (sender, args) => { if (accountManager.GetCurrentStore() is { } store) { diff --git a/BTCPayApp.UI/Util/RequiredIfAttribute.cs b/BTCPayApp.UI/Util/RequiredIfAttribute.cs index c390911b..389006d0 100644 --- a/BTCPayApp.UI/Util/RequiredIfAttribute.cs +++ b/BTCPayApp.UI/Util/RequiredIfAttribute.cs @@ -2,14 +2,20 @@ namespace BTCPayApp.UI.Util; -public class RequiredIfAttribute(string otherProperty, object targetValue) : ValidationAttribute +public enum RequiredIfTargetValue +{ + NotNull +} + +public class RequiredIfAttribute(string otherProperty, object? targetValue) : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { var otherPropertyValue = validationContext.ObjectType .GetProperty(otherProperty)? .GetValue(validationContext.ObjectInstance); - if (otherPropertyValue is null || !otherPropertyValue.Equals(targetValue)) return ValidationResult.Success; + if (otherPropertyValue is null || targetValue is RequiredIfTargetValue.NotNull || + !otherPropertyValue.Equals(targetValue)) return ValidationResult.Success; return string.IsNullOrWhiteSpace(value?.ToString()) ? new ValidationResult(ErrorMessage ?? "This field is required.") : ValidationResult.Success; diff --git a/README.md b/README.md index 51bbc445..e0a8b5be 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,30 @@ Click the Connect button, use `http://localhost:14142` as the server URL and an After the first run of `DEV ALL` on a Linux machine with a new .NET setup, you may run into the [dotnet dev-certs - Untrusted Root](https://github.com/dotnet/aspnetcore/issues/41503) error, and you may find a solution at the [following link](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs) + +## Lightning Channels + +To establish channels for local testing, you can use the Docker Lightning CLI scripts like this: + +```bash +# The scripts are inside the submodule's test directory +cd submodules/btcpayserver/BTCPayServer.Tests + +# Run the general channel setup for the BTCPay LN nodes +./docker-lightning-channel-setup.sh +``` + +Besides establishing channel connections between the various BTCPay LN testing nodes, this will also give you their node URIs. + +### Create channel + +- App: Go to the [Lightning Channels view](https://localhost:7016/settings/lightning/channels) and connect to one of the peer node URIs from the comand above +- App: Open Channel to that peer +- CLI: Run `./docker-bitcoin-generate.sh 5` +- App: Refresh the Lightning Channels view and see if the channel got confirmed +- App: Go to the [Send view](https://localhost:7016/send) and see if your Lightning local/outbound capacity is present + +### Send payment + +- CLI: Generate an invoice with the peer script, e.g. `./docker-customer-lncli.sh addinvoice --amt 10000` +- App: Pay the Lightning invoice on the Send view