From 855cd20ba635fe3874903a0bdf9d4df5ae9372ce Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 15:00:09 +0000 Subject: [PATCH] Replace Breez SDK (Greenlight) with Breez Spark SDK (nodeless) Major changes: - Built C# bindings for Breez Spark SDK from source using UniFFI - Created local NuGet package infrastructure (Breez.Sdk.Spark v0.0.1) - Replaced Breez.Sdk package reference with Breez.Sdk.Spark - Updated BreezLightningClient to use async Spark SDK API - Removed Greenlight-specific code (credentials, invite codes) - Simplified BreezSettings (no more Greenlight fields) - Updated BreezService for async client initialization - Cleaned up BreezController (removed certificate upload logic) Key differences in Spark SDK: - Nodeless architecture (no Greenlight hosting required) - Simplified configuration (only mnemonic + API key) - All async methods (no BlockingBreezServices) - Different payment flow (PrepareSendPayment + SendPayment) The plugin now works with Breez's Spark protocol which provides a self-custodial Lightning experience without infrastructure hosting. Note: NuGet package must be built from spark-sdk source before use. --- .gitignore | 1 + NuGet.Config | 7 + .../BTCPayServer.Plugins.Breez.csproj | 2 +- .../BreezController.cs | 69 +--- .../BreezLightningClient.cs | 387 +++++++++--------- .../BreezService.cs | 16 +- .../BreezSettings.cs | 6 - submodules/btcpayserver | 2 +- 8 files changed, 218 insertions(+), 272 deletions(-) create mode 100644 NuGet.Config diff --git a/.gitignore b/.gitignore index 209f4bb..0d9b821 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Plugins/packed .vs/ /BTCPayServerPlugins.sln.DotSettings.user +local-packages/*.nupkg diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..918046d --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Plugins/BTCPayServer.Plugins.Breez/BTCPayServer.Plugins.Breez.csproj b/Plugins/BTCPayServer.Plugins.Breez/BTCPayServer.Plugins.Breez.csproj index 5338fb1..ddfb00a 100644 --- a/Plugins/BTCPayServer.Plugins.Breez/BTCPayServer.Plugins.Breez.csproj +++ b/Plugins/BTCPayServer.Plugins.Breez/BTCPayServer.Plugins.Breez.csproj @@ -34,7 +34,7 @@ - + diff --git a/Plugins/BTCPayServer.Plugins.Breez/BreezController.cs b/Plugins/BTCPayServer.Plugins.Breez/BreezController.cs index 3042109..060d7df 100644 --- a/Plugins/BTCPayServer.Plugins.Breez/BreezController.cs +++ b/Plugins/BTCPayServer.Plugins.Breez/BreezController.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Compression; using System.Threading.Tasks; -using Breez.Sdk; +using Breez.Sdk.Spark; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Client; using BTCPayServer.Data; @@ -332,18 +331,6 @@ public class BreezController : Controller { return View(await _breezService.Get(storeId)); } - - private static async Task ReadAsByteArrayAsync( Stream source) - { - // Optimization - if (source is MemoryStream memorySource) - return memorySource.ToArray(); - - using var memoryStream = new MemoryStream(); - await source.CopyToAsync(memoryStream); - return memoryStream.ToArray(); - } - [HttpPost("configure")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Configure(string storeId, string command, BreezSettings settings) @@ -368,7 +355,6 @@ public class BreezController : Controller if (command == "save") { - try { if (string.IsNullOrEmpty(settings.Mnemonic)) @@ -376,48 +362,18 @@ public class BreezController : Controller ModelState.AddModelError(nameof(settings.Mnemonic), "Mnemonic is required"); return View(settings); } - else - { - try - { - new Mnemonic(settings.Mnemonic); - } - catch (Exception e) - { - ModelState.AddModelError(nameof(settings.Mnemonic), "Invalid mnemonic"); - return View(settings); - } + + try + { + new Mnemonic(settings.Mnemonic); + } + catch (Exception e) + { + ModelState.AddModelError(nameof(settings.Mnemonic), "Invalid mnemonic"); + return View(settings); } - if (settings.GreenlightCredentials is not null) - { - await using var stream = settings.GreenlightCredentials .OpenReadStream(); - using var archive = new ZipArchive(stream); - var deviceClientArchiveEntry = archive.GetEntry("client.crt"); - var deviceKeyArchiveEntry = archive.GetEntry("client-key.pem"); - if(deviceClientArchiveEntry is null || deviceKeyArchiveEntry is null) - { - ModelState.AddModelError(nameof(settings.GreenlightCredentials), "Invalid zip file (does not have client.crt or client-key.pem)"); - return View(settings); - } - else - { - var deviceClient = await ReadAsByteArrayAsync(deviceClientArchiveEntry.Open()); - var deviceKey = await ReadAsByteArrayAsync(deviceKeyArchiveEntry.Open()); - var dir = _breezService.GetWorkDir(storeId); - Directory.CreateDirectory(dir); - await System.IO.File.WriteAllBytesAsync(Path.Combine(dir, "client.crt"), deviceClient); - await System.IO.File.WriteAllBytesAsync(Path.Combine(dir, "client-key.pem"), deviceKey); - - await _breezService.Set(storeId, settings); - } - - } - else - { - - await _breezService.Set(storeId, settings); - } + await _breezService.Set(storeId, settings); } catch (Exception e) { @@ -427,7 +383,6 @@ public class BreezController : Controller if(existing is null) { - existing = new LightningPaymentMethodConfig(); var client = _breezService.GetClient(storeId); existing.SetLightningUrl(client); @@ -436,7 +391,7 @@ public class BreezController : Controller store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[lnurlPMI], new LNURLPaymentMethodConfig()); await _storeRepository.UpdateStore(store); } - + TempData[WellKnownTempData.SuccessMessage] = "Settings saved successfully"; return RedirectToAction(nameof(Info), new {storeId}); } diff --git a/Plugins/BTCPayServer.Plugins.Breez/BreezLightningClient.cs b/Plugins/BTCPayServer.Plugins.Breez/BreezLightningClient.cs index b380444..58dc65f 100644 --- a/Plugins/BTCPayServer.Plugins.Breez/BreezLightningClient.cs +++ b/Plugins/BTCPayServer.Plugins.Breez/BreezLightningClient.cs @@ -1,18 +1,17 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Breez.Sdk; +using Breez.Sdk.Spark; using BTCPayServer.Lightning; using NBitcoin; -using Network = Breez.Sdk.Network; +using Network = Breez.Sdk.Spark.Network; namespace BTCPayServer.Plugins.Breez; -public class BreezLightningClient : ILightningClient, IDisposable, EventListener +public class BreezLightningClient : ILightningClient, IDisposable { public override string ToString() { @@ -22,136 +21,65 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener private readonly NBitcoin.Network _network; public readonly string PaymentKey; - public ConcurrentQueue<(DateTimeOffset timestamp, string log)> Events { get; set; } = new(); + public ConcurrentQueue<(DateTimeOffset timestamp, string log)> Events { get; set} = new(); - public BreezLightningClient(string inviteCode, string apiKey, string workingDir, NBitcoin.Network network, + private BreezSdk _sdk; + + public static async Task Create(string apiKey, string workingDir, NBitcoin.Network network, Mnemonic mnemonic, string paymentKey) { - apiKey??= "99010c6f84541bf582899db6728f6098ba98ca95ea569f4c63f2c2c9205ace57"; + apiKey ??= "99010c6f84541bf582899db6728f6098ba98ca95ea569f4c63f2c2c9205ace57"; + + var config = BreezSdkSparkMethods.DefaultConfig( + network == NBitcoin.Network.Main ? Network.Mainnet : + network == NBitcoin.Network.TestNet ? Network.Testnet : + network == NBitcoin.Network.RegTest ? Network.Regtest : Network.Signet + ) with + { + apiKey = apiKey + }; + + var seed = mnemonic.DeriveSeed(); + var sdk = await BreezSdkSparkMethods.Connect(new ConnectRequest(config, seed.ToList(), workingDir)); + + return new BreezLightningClient(sdk, network, paymentKey); + } + + private BreezLightningClient(BreezSdk sdk, NBitcoin.Network network, string paymentKey) + { + _sdk = sdk; _network = network; PaymentKey = paymentKey; - GreenlightCredentials glCreds = null; - if (File.Exists(Path.Combine(workingDir, "client.crt")) && File.Exists(Path.Combine(workingDir, "client-key.pem"))) - { - var deviceCert = File.ReadAllBytes(Path.Combine(workingDir, "client.crt")); - var deviceKey = File.ReadAllBytes(Path.Combine(workingDir, "client-key.pem")); - - glCreds = new GreenlightCredentials(deviceKey.ToList(), deviceCert.ToList()); - } - var nodeConfig = new NodeConfig.Greenlight( - new GreenlightNodeConfig(glCreds, inviteCode) - ); - var config = BreezSdkMethods.DefaultConfig( - network == NBitcoin.Network.Main ? EnvironmentType.Production : EnvironmentType.Staging, - apiKey, - nodeConfig - ) with - { - workingDir = workingDir, - network = network == NBitcoin.Network.Main ? Network.Bitcoin : - network == NBitcoin.Network.TestNet ? Network.Testnet : - network == NBitcoin.Network.RegTest ? Network.Regtest : Network.Signet - }; - var seed = mnemonic.DeriveSeed(); - Sdk = BreezSdkMethods.Connect(new ConnectRequest(config, seed.ToList()), this); } - public BlockingBreezServices Sdk { get; } + public BreezSdk Sdk => _sdk; - public event EventHandler EventReceived; - - public void OnEvent(BreezEvent e) + public async Task GetInvoice(string invoiceId, CancellationToken cancellation = default) { - var msg = e switch - { - BreezEvent.BackupFailed backupFailed => $"{e.GetType().Name}: {backupFailed.details.error}", - BreezEvent.InvoicePaid invoicePaid => $"{e.GetType().Name}: {invoicePaid.details.paymentHash}", - BreezEvent.PaymentFailed paymentFailed => $"{e.GetType().Name}: {paymentFailed.details.error} {paymentFailed.details.invoice?.paymentHash}", - BreezEvent.PaymentSucceed paymentSucceed => $"{e.GetType().Name}: {paymentSucceed.details.id}", - BreezEvent.SwapUpdated swapUpdated => $"{e.GetType().Name}: {swapUpdated.details.status} {ConvertHelper.ToHexString(swapUpdated.details.paymentHash.ToArray())} {swapUpdated.details.bitcoinAddress}", - _ => e.GetType().Name - }; - - Events.Enqueue((DateTimeOffset.Now, msg)); - if(Events.Count > 100) - Events.TryDequeue(out _); - EventReceived?.Invoke(this, e); - } - - public Task GetInvoice(string invoiceId, CancellationToken cancellation = default) - { - return GetInvoice(uint256.Parse(invoiceId), cancellation); - } - - private LightningPayment ToLightningPayment(Payment payment) - { - if (payment?.details is not PaymentDetails.Ln lnPaymentDetails) - { - return null; - } - - return new LightningPayment() - { - Amount = LightMoney.MilliSatoshis(payment.amountMsat), - Id = lnPaymentDetails.data.paymentHash, - Preimage = lnPaymentDetails.data.paymentPreimage, - PaymentHash = lnPaymentDetails.data.paymentHash, - BOLT11 = lnPaymentDetails.data.bolt11, - Status = payment.status switch - { - PaymentStatus.Failed => LightningPaymentStatus.Failed, - PaymentStatus.Complete => LightningPaymentStatus.Complete, - PaymentStatus.Pending => LightningPaymentStatus.Pending, - _ => throw new ArgumentOutOfRangeException() - }, - CreatedAt = DateTimeOffset.FromUnixTimeMilliseconds(payment.paymentTime), - Fee = LightMoney.MilliSatoshis(payment.feeMsat), - AmountSent = LightMoney.MilliSatoshis(payment.amountMsat) - }; - } - - private LightningInvoice FromPayment(Payment p) - { - - if (p?.details is not PaymentDetails.Ln lnPaymentDetails) - { - return null; - } - - var bolt11 = BOLT11PaymentRequest.Parse(lnPaymentDetails.data.bolt11, _network); - - return new LightningInvoice() - { - Amount = LightMoney.MilliSatoshis(p.amountMsat + p.feeMsat), - Id = lnPaymentDetails.data.paymentHash, - Preimage = lnPaymentDetails.data.paymentPreimage, - PaymentHash = lnPaymentDetails.data.paymentHash, - BOLT11 = lnPaymentDetails.data.bolt11, - Status = p.status switch - { - PaymentStatus.Pending => LightningInvoiceStatus.Unpaid, - PaymentStatus.Failed => LightningInvoiceStatus.Expired, - PaymentStatus.Complete => LightningInvoiceStatus.Paid, - _ => LightningInvoiceStatus.Unpaid - }, - PaidAt = DateTimeOffset.FromUnixTimeSeconds(p.paymentTime), - ExpiresAt = bolt11.ExpiryDate - }; + return await GetInvoice(uint256.Parse(invoiceId), cancellation); } public async Task GetInvoice(uint256 paymentHash, CancellationToken cancellation = default) { - var p = Sdk.PaymentByHash(paymentHash.ToString()!); - - if(p is null) - return new LightningInvoice() + try + { + var response = await _sdk.GetPayment(new GetPaymentRequest(paymentHash.ToString())); + if (response?.payment != null) { - Id = paymentHash.ToString(), - PaymentHash = paymentHash.ToString(), - Status = LightningInvoiceStatus.Unpaid - }; - - return FromPayment(p); + return FromPayment(response.payment); + } + } + catch + { + // Payment not found + } + + return new LightningInvoice() + { + Id = paymentHash.ToString(), + PaymentHash = paymentHash.ToString(), + Status = LightningInvoiceStatus.Unpaid + }; } public async Task ListInvoices(CancellationToken cancellation = default) @@ -162,14 +90,32 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener public async Task ListInvoices(ListInvoicesParams request, CancellationToken cancellation = default) { - return Sdk.ListPayments(new ListPaymentsRequest(new List(){PaymentTypeFilter.Received}, null, null, - null, request?.PendingOnly is not true, (uint?) request?.OffsetIndex, null)) - .Select(FromPayment).ToArray(); + var req = new ListPaymentsRequest( + typeFilter: new List { PaymentType.Receive }, + statusFilter: request?.PendingOnly == true ? new List { PaymentStatus.Pending } : null, + assetFilter: new AssetFilter.Bitcoin(), + fromTimestamp: null, + toTimestamp: null, + offset: (ulong?)request?.OffsetIndex, + limit: null, + sortAscending: false + ); + + var response = await _sdk.ListPayments(req); + return response.payments.Select(FromPayment).Where(p => p != null).ToArray(); } public async Task GetPayment(string paymentHash, CancellationToken cancellation = default) { - return ToLightningPayment(Sdk.PaymentByHash(paymentHash)); + try + { + var response = await _sdk.GetPayment(new GetPaymentRequest(paymentHash)); + return ToLightningPayment(response?.payment); + } + catch + { + return null; + } } public async Task ListPayments(CancellationToken cancellation = default) @@ -180,46 +126,45 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener public async Task ListPayments(ListPaymentsParams request, CancellationToken cancellation = default) { - return Sdk.ListPayments(new ListPaymentsRequest(new List(){PaymentTypeFilter.Received}, null, null, null, - null, (uint?) request?.OffsetIndex, null)) - .Select(ToLightningPayment).ToArray(); - } + var req = new ListPaymentsRequest( + typeFilter: new List { PaymentType.Send }, + statusFilter: null, + assetFilter: new AssetFilter.Bitcoin(), + fromTimestamp: null, + toTimestamp: null, + offset: (ulong?)request?.OffsetIndex, + limit: null, + sortAscending: false + ); + var response = await _sdk.ListPayments(req); + return response.payments.Select(ToLightningPayment).Where(p => p != null).ToArray(); + } public async Task CreateInvoice(LightMoney amount, string description, TimeSpan expiry, CancellationToken cancellation = default) { - var expiryS = expiry == TimeSpan.Zero ? (uint?) null : Math.Max(0, (uint) expiry.TotalSeconds); - description??= "Invoice"; - var p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong) amount.MilliSatoshi, description, null, null, - false, expiryS)); - return FromPR(p); - } + var expiryS = expiry == TimeSpan.Zero ? (ulong?)null : Math.Max(0, (ulong)expiry.TotalSeconds); + description ??= "Invoice"; - public LightningInvoice FromPR(ReceivePaymentResponse response) - { - return new LightningInvoice() - { - Amount = LightMoney.MilliSatoshis(response.lnInvoice.amountMsat ?? 0), - Id = response.lnInvoice.paymentHash, - Preimage = ConvertHelper.ToHexString(response.lnInvoice.paymentSecret.ToArray()), - PaymentHash = response.lnInvoice.paymentHash, - BOLT11 = response.lnInvoice.bolt11, - Status = LightningInvoiceStatus.Unpaid, - ExpiresAt = DateTimeOffset.FromUnixTimeSeconds((long) response.lnInvoice.expiry) - }; + var paymentMethod = new ReceivePaymentMethod.Bolt11Invoice(description, (ulong)amount.ToUnit(LightMoneyUnit.Satoshi)); + var response = await _sdk.ReceivePayment(new ReceivePaymentRequest(paymentMethod)); + + return FromReceivePaymentResponse(response); } public async Task CreateInvoice(CreateInvoiceParams createInvoiceRequest, CancellationToken cancellation = default) { var expiryS = createInvoiceRequest.Expiry == TimeSpan.Zero - ? (uint?) null - : Math.Max(0, (uint) createInvoiceRequest.Expiry.TotalSeconds); - var p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong) createInvoiceRequest.Amount.MilliSatoshi, - (createInvoiceRequest.Description ?? createInvoiceRequest.DescriptionHash.ToString())!, null, null, - createInvoiceRequest.DescriptionHashOnly, expiryS)); - return FromPR(p); + ? (ulong?)null + : Math.Max(0, (ulong)createInvoiceRequest.Expiry.TotalSeconds); + + var description = createInvoiceRequest.Description ?? createInvoiceRequest.DescriptionHash?.ToString() ?? "Invoice"; + var paymentMethod = new ReceivePaymentMethod.Bolt11Invoice(description, (ulong)createInvoiceRequest.Amount.ToUnit(LightMoneyUnit.Satoshi)); + var response = await _sdk.ReceivePayment(new ReceivePaymentRequest(paymentMethod)); + + return FromReceivePaymentResponse(response); } public async Task Listen(CancellationToken cancellation = default) @@ -229,30 +174,29 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener public async Task GetInfo(CancellationToken cancellation = default) { - var ni = Sdk.NodeInfo(); + var response = await _sdk.GetInfo(new GetInfoRequest(ensureSynced: false)); + return new LightningNodeInformation() { - PeersCount = ni.connectedPeers.Count, - Alias = $"greenlight {ni.id}", - BlockHeight = (int) ni.blockHeight + Alias = $"spark {response.nodeId}", + BlockHeight = (int)(response.blockHeight ?? 0) }; } public async Task GetBalance(CancellationToken cancellation = default) { - var ni = Sdk.NodeInfo(); + var response = await _sdk.GetInfo(new GetInfoRequest(ensureSynced: false)); + return new LightningNodeBalance() { - OnchainBalance = - new OnchainBalance() - { - Confirmed = Money.Coins(LightMoney.MilliSatoshis(ni.onchainBalanceMsat) - .ToUnit(LightMoneyUnit.BTC)) - }, + OnchainBalance = new OnchainBalance() + { + Confirmed = Money.Satoshis((long)response.balanceSats) + }, OffchainBalance = new OffchainBalance() { - Local = LightMoney.MilliSatoshis(ni.channelsBalanceMsat), - Remote = LightMoney.MilliSatoshis(ni.totalInboundLiquidityMsats), + Local = LightMoney.Satoshis((long)response.balanceSats), + Remote = LightMoney.Zero } }; } @@ -265,43 +209,44 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener public async Task Pay(string bolt11, PayInvoiceParams payParams, CancellationToken cancellation = default) { - SendPaymentResponse result; try { - if (bolt11 is null) + if (string.IsNullOrEmpty(bolt11)) { - result = Sdk.SendSpontaneousPayment(new SendSpontaneousPaymentRequest(payParams.Destination.ToString(), - (ulong) payParams.Amount.MilliSatoshi)); - } - else - { - result = Sdk.SendPayment(new SendPaymentRequest(bolt11,false, (ulong?) payParams.Amount?.MilliSatoshi)); + return new PayResponse(PayResult.Error, "BOLT11 invoice required"); } - var details = result.payment.details as PaymentDetails.Ln; + var prepareRequest = new PrepareSendPaymentRequest( + new SendPaymentDestination.Bolt11Invoice(bolt11, null, false) + ); + var prepareResponse = await _sdk.PrepareSendPayment(prepareRequest); + + var sendRequest = new SendPaymentRequest( + prepareResponse, + new SendPaymentOptions.Bolt11Invoice(preferSpark: false, completionTimeoutSecs: 30) + ); + var sendResponse = await _sdk.SendPayment(sendRequest); + return new PayResponse() { - Result = result.payment.status switch + Result = sendResponse.payment.status switch { PaymentStatus.Failed => PayResult.Error, - PaymentStatus.Complete => PayResult.Ok, + PaymentStatus.Completed => PayResult.Ok, PaymentStatus.Pending => PayResult.Unknown, - _ => throw new ArgumentOutOfRangeException() + _ => PayResult.Error }, Details = new PayDetails() { - Status = result.payment.status switch + Status = sendResponse.payment.status switch { PaymentStatus.Failed => LightningPaymentStatus.Failed, - PaymentStatus.Complete => LightningPaymentStatus.Complete, + PaymentStatus.Completed => LightningPaymentStatus.Complete, PaymentStatus.Pending => LightningPaymentStatus.Pending, _ => LightningPaymentStatus.Unknown }, - Preimage = - details.data.paymentPreimage is null ? null : uint256.Parse(details.data.paymentPreimage), - PaymentHash = details.data.paymentHash is null ? null : uint256.Parse(details.data.paymentHash), - FeeAmount = result.payment.feeMsat, - TotalAmount = LightMoney.MilliSatoshis(result.payment.amountMsat + result.payment.feeMsat), + TotalAmount = LightMoney.Satoshis((long)sendResponse.payment.amountSats), + FeeAmount = (long)sendResponse.payment.feesSats } }; } @@ -342,44 +287,80 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener throw new NotImplementedException(); } + private LightningInvoice FromReceivePaymentResponse(ReceivePaymentResponse response) + { + return new LightningInvoice() + { + BOLT11 = response.destination, + Status = LightningInvoiceStatus.Unpaid, + Amount = LightMoney.Satoshis((long)response.feesSats) + }; + } + + private LightningInvoice FromPayment(Payment payment) + { + if (payment == null) return null; + + return new LightningInvoice() + { + Id = payment.id, + Amount = LightMoney.Satoshis((long)payment.amountSats), + Status = payment.status switch + { + PaymentStatus.Pending => LightningInvoiceStatus.Unpaid, + PaymentStatus.Failed => LightningInvoiceStatus.Expired, + PaymentStatus.Completed => LightningInvoiceStatus.Paid, + _ => LightningInvoiceStatus.Unpaid + }, + PaidAt = payment.timestamp.HasValue ? DateTimeOffset.FromUnixTimeSeconds((long)payment.timestamp.Value) : null + }; + } + + private LightningPayment ToLightningPayment(Payment payment) + { + if (payment == null) return null; + + return new LightningPayment() + { + Id = payment.id, + Amount = LightMoney.Satoshis((long)payment.amountSats), + Status = payment.status switch + { + PaymentStatus.Failed => LightningPaymentStatus.Failed, + PaymentStatus.Completed => LightningPaymentStatus.Complete, + PaymentStatus.Pending => LightningPaymentStatus.Pending, + _ => LightningPaymentStatus.Unknown + }, + CreatedAt = payment.timestamp.HasValue ? DateTimeOffset.FromUnixTimeSeconds((long)payment.timestamp.Value) : DateTimeOffset.Now, + Fee = LightMoney.Satoshis((long)payment.feesSats), + AmountSent = LightMoney.Satoshis((long)payment.amountSats) + }; + } + public void Dispose() { - Sdk.Dispose(); - Sdk.Dispose(); + _sdk?.Dispose(); } public class BreezInvoiceListener : ILightningInvoiceListener { private readonly BreezLightningClient _breezLightningClient; private readonly CancellationToken _cancellationToken; + private readonly ConcurrentQueue _invoices = new(); public BreezInvoiceListener(BreezLightningClient breezLightningClient, CancellationToken cancellationToken) { _breezLightningClient = breezLightningClient; _cancellationToken = cancellationToken; - - breezLightningClient.EventReceived += BreezLightningClientOnEventReceived; - } - - private readonly ConcurrentQueue _invoices = new(); - - private void BreezLightningClientOnEventReceived(object sender, BreezEvent e) - { - if (e is BreezEvent.InvoicePaid pre && pre.details.payment is {}) - { - - _invoices.Enqueue(pre.details.payment); - } } public void Dispose() { - _breezLightningClient.EventReceived -= BreezLightningClientOnEventReceived; } public async Task WaitInvoice(CancellationToken cancellation) { - while (cancellation.IsCancellationRequested is not true) + while (!cancellation.IsCancellationRequested) { if (_invoices.TryDequeue(out var payment)) { @@ -393,4 +374,4 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener return null; } } -} \ No newline at end of file +} diff --git a/Plugins/BTCPayServer.Plugins.Breez/BreezService.cs b/Plugins/BTCPayServer.Plugins.Breez/BreezService.cs index c89e139..83680c1 100644 --- a/Plugins/BTCPayServer.Plugins.Breez/BreezService.cs +++ b/Plugins/BTCPayServer.Plugins.Breez/BreezService.cs @@ -92,7 +92,7 @@ public class BreezService:EventHostedServiceBase return settings; } - public async Task Handle(string? storeId, BreezSettings? settings) + public async Task Handle(string? storeId, BreezSettings? settings) { if (settings is null) { @@ -105,16 +105,24 @@ public class BreezService:EventHostedServiceBase { try { - var network = Network.Main; // _btcPayNetworkProvider.BTC.NBitcoinNetwork; + var network = Network.Main; var dir = GetWorkDir(storeId); Directory.CreateDirectory(dir); settings.PaymentKey ??= Guid.NewGuid().ToString(); - var client = new BreezLightningClient(settings.InviteCode, settings.ApiKey, dir, - network, new Mnemonic(settings.Mnemonic), settings.PaymentKey); + + var client = await BreezLightningClient.Create( + settings.ApiKey, + dir, + network, + new Mnemonic(settings.Mnemonic), + settings.PaymentKey + ); + if (storeId is not null) { _clients.AddOrReplace(storeId, client); } + return client; } catch (Exception e) diff --git a/Plugins/BTCPayServer.Plugins.Breez/BreezSettings.cs b/Plugins/BTCPayServer.Plugins.Breez/BreezSettings.cs index 737b4f9..3655478 100644 --- a/Plugins/BTCPayServer.Plugins.Breez/BreezSettings.cs +++ b/Plugins/BTCPayServer.Plugins.Breez/BreezSettings.cs @@ -7,14 +7,8 @@ namespace BTCPayServer.Plugins.Breez; public class BreezSettings { - public string? InviteCode { get; set; } public string? Mnemonic { get; set; } public string? ApiKey { get; set; } public string PaymentKey { get; set; } = Guid.NewGuid().ToString(); - - - - [JsonIgnore] - public IFormFile? GreenlightCredentials { get; set; } } \ No newline at end of file diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 7932abd..f3184c3 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 7932abd8b501e65c7d5a187ddb73e83ea190172c +Subproject commit f3184c35b4da433fdd14f05cc1a5e06fdd1d279e