From 3db5b5537d5f43cef0ecf98711dcda8b685532a2 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 May 2024 08:35:27 +0200 Subject: [PATCH] add nwc and bump nostr --- .../BTCPayServer.Plugins.NIP05.csproj | 4 +- .../BTCPayServer.Plugins.NIP05/Nip05Plugin.cs | 6 + .../NostrClientPool.cs | 77 +++ .../NostrClientWrapper.cs | 48 ++ .../NostrWalletConnectLightningClient.cs | 469 ++++++++++++++++++ ...ConnectLightningConnectionStringHandler.cs | 67 +++ .../Shared/NWC/LNPaymentMethodSetupTab.cshtml | 41 ++ .../BTCPayServer.Plugins.Wabisabi.csproj | 2 +- .../BTCPayServer.Plugins.Wabisabi/Nostr.cs | 9 +- submodules/btcpayserver | 2 +- 10 files changed, 717 insertions(+), 8 deletions(-) create mode 100644 Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs create mode 100644 Plugins/BTCPayServer.Plugins.NIP05/NostrClientWrapper.cs create mode 100644 Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs create mode 100644 Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs create mode 100644 Plugins/BTCPayServer.Plugins.NIP05/Views/Shared/NWC/LNPaymentMethodSetupTab.cshtml diff --git a/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj b/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj index 79d0a24..9a1e730 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj +++ b/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj @@ -11,7 +11,7 @@ Nostr Allows you to verify your nostr account with NIP5 and zap like the rest of the crazies - 1.1.3 + 1.1.4 true @@ -36,7 +36,7 @@ - + diff --git a/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs b/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs index 09bf812..8b317d7 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs +++ b/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs @@ -2,6 +2,7 @@ using System.Text; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Services; +using BTCPayServer.Lightning; using Microsoft.Extensions.DependencyInjection; namespace BTCPayServer.Plugins.NIP05 @@ -17,10 +18,15 @@ namespace BTCPayServer.Plugins.NIP05 { applicationBuilder.AddSingleton(new UIExtension("Nip05Nav", "store-integrations-nav")); + applicationBuilder.AddSingleton(new UIExtension("NWC/LNPaymentMethodSetupTab", "ln-payment-method-setup-tab")); + applicationBuilder.AddSingleton(); applicationBuilder.AddSingleton(); applicationBuilder.AddSingleton(); applicationBuilder.AddHostedService(sp => sp.GetRequiredService()); + applicationBuilder.AddSingleton(); + applicationBuilder.AddSingleton(provider => provider.GetRequiredService()); + base.Execute(applicationBuilder); } diff --git a/Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs new file mode 100644 index 0000000..1e66750 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using NNostr.Client; +using NNostr.Client.Protocols; + +namespace BTCPayServer.Plugins.NIP05; + +public class NostrClientPool +{ + private static readonly ConcurrentDictionary _clientPool = new(); + + private static readonly Timer _cleanupTimer; + + static NostrClientPool() + { + _cleanupTimer = new Timer(CleanupExpiredClients, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); + } + + public static (INostrClient, IDisposable) GetClient(string connstring) + { + var connParams = NIP47.ParseUri(new Uri(connstring)); + + var clientWrapper = _clientPool.GetOrAdd(connstring.ToString(), + k => new NostrClientWrapper(new CompositeNostrClient(connParams.relays))); + + clientWrapper.IncrementUsage(); + + return (clientWrapper.Client, new UsageDisposable(clientWrapper)); + } + public static async Task<(INostrClient, IDisposable)> GetClientAndConnect(string connstring, CancellationToken token) + { + var result = GetClient(connstring); + + await result.Item1.ConnectAndWaitUntilConnected(token, CancellationToken.None); + + return result; + } + + public static void KillClient(string connstring) + { + if (_clientPool.TryRemove(connstring, out var clientWrapper)) + { + clientWrapper.Dispose(); + } + } + + private static void CleanupExpiredClients(object state) + { + foreach (var key in _clientPool.Keys) + { + if (_clientPool[key].IsExpired()) + { + if (_clientPool.TryRemove(key, out var clientWrapper)) + { + clientWrapper.Dispose(); + } + } + } + } + + private class UsageDisposable : IDisposable + { + private readonly NostrClientWrapper _clientWrapper; + + public UsageDisposable(NostrClientWrapper clientWrapper) + { + _clientWrapper = clientWrapper; + } + + public void Dispose() + { + _clientWrapper.DecrementUsage(); + } + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.NIP05/NostrClientWrapper.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrClientWrapper.cs new file mode 100644 index 0000000..27efd89 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.NIP05/NostrClientWrapper.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using NNostr.Client; + +namespace BTCPayServer.Plugins.NIP05; + +public class NostrClientWrapper : IDisposable +{ + public INostrClient Client { get; private set; } + private int _usageCount = 0; + private bool _isDisposed = false; + private DateTimeOffset _lastUsed; + + public NostrClientWrapper(INostrClient client) + { + Client = client; + _lastUsed = DateTimeOffset.UtcNow; + } + + public void IncrementUsage() + { + _lastUsed = DateTimeOffset.UtcNow; + Interlocked.Increment(ref _usageCount); + } + + public void DecrementUsage() + { + _lastUsed = DateTimeOffset.UtcNow; + if (Interlocked.Decrement(ref _usageCount) == 0 && IsExpired()) + { + Dispose(); + } + } + + public bool IsExpired() + { + return DateTimeOffset.UtcNow - _lastUsed > TimeSpan.FromMinutes(5); + } + + public void Dispose() + { + if (!_isDisposed) + { + Client.Dispose(); + _isDisposed = true; + } + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs new file mode 100644 index 0000000..c71e4e3 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs @@ -0,0 +1,469 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using BTCPayServer.Lightning; +using NBitcoin; +using NBitcoin.Secp256k1; +using NNostr.Client; +using NNostr.Client.Protocols; + +namespace BTCPayServer.Plugins.NIP05; + +public class NostrWalletConnectLightningClient : ILightningClient +{ + private readonly Uri _uri; + private readonly Network _network; + private readonly (string[] Commands, string[] Notifications) _commands; + private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectParams; + + public NostrWalletConnectLightningClient(Uri uri, Network network, + (string[] Commands, string[] Notifications) commands) + { + _uri = uri; + _network = network; + _commands = commands; + _connectParams = NIP47.ParseUri(uri); + } + + public override string ToString() + { + return $"type=nwc;key={_uri}"; + } + + + public async Task GetInvoice(string invoiceId, + CancellationToken cancellation = new CancellationToken()) + { + return await GetInvoice(uint256.Parse(invoiceId), cancellation); + } + + private static LightningInvoice? ToLightningInvoice(NIP47.Nip47Transaction tx, Network network) + { + if (tx.Type != "incoming") + { + return null; + } + + var isPaid = tx.SettledAt.HasValue; + var invoice = BOLT11PaymentRequest.Parse(tx.Invoice, network); + var expiresAt = tx.ExpiresAt is not null + ? DateTimeOffset.FromUnixTimeSeconds(tx.ExpiresAt.Value) + : invoice.ExpiryDate; + var expired = !isPaid && expiresAt < DateTimeOffset.UtcNow; + var s = tx.SettledAt is not null + ? DateTimeOffset.FromUnixTimeSeconds(tx.SettledAt.Value) + : (DateTimeOffset?) null; + return new LightningInvoice() + { + PaymentHash = tx.PaymentHash, + Amount = LightMoney.MilliSatoshis(tx.AmountMsats), + Preimage = tx.Preimage, + Id = tx.PaymentHash, + Status = isPaid ? LightningInvoiceStatus.Paid : + expired ? LightningInvoiceStatus.Expired : LightningInvoiceStatus.Unpaid, + PaidAt = s, + ExpiresAt = expiresAt, + AmountReceived = isPaid ? LightMoney.MilliSatoshis(tx.AmountMsats) : LightMoney.Zero, + BOLT11 = tx.Invoice + }; + } + + + public async Task GetInvoice(uint256 paymentHash, + CancellationToken cancellation = new CancellationToken()) + { + var (nostrClient, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var tx = await nostrClient.SendNIP47Request(_connectParams.pubkey, + _connectParams.secret, + new NIP47.LookupInvoiceRequest() + { + PaymentHash = paymentHash.ToString() + }, cancellation); + return ToLightningInvoice(tx, _network)!; + } + } + + public async Task ListInvoices(CancellationToken cancellation = new CancellationToken()) + { + return await ListInvoices(new ListInvoicesParams(), cancellation); + } + + public async Task ListInvoices(ListInvoicesParams request, + CancellationToken cancellation = new CancellationToken()) + { + var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var response = await client.SendNIP47Request(_connectParams.pubkey, + _connectParams.secret, + new NIP47.ListTransactionsRequest() + { + Type = "incoming", + Offset = (int) (request.OffsetIndex ?? 0), + Unpaid = request.PendingOnly ?? false, + }, cancellation); + + return response.Transactions.Select(transaction => ToLightningInvoice(transaction, _network)) + .Where(i => i is not null).ToArray()!; + } + } + + + private LightningPayment? ToLightningPayment(NIP47.Nip47Transaction tx) + { + if (tx.Type != "outgoing") + { + return null; + } + + var isPaid = tx.SettledAt.HasValue || !string.IsNullOrEmpty(tx.Preimage); + var invoice = BOLT11PaymentRequest.Parse(tx.Invoice, _network); + var expiresAt = tx.ExpiresAt is not null + ? DateTimeOffset.FromUnixTimeSeconds(tx.ExpiresAt.Value) + : invoice.ExpiryDate; + var created = DateTimeOffset.FromUnixTimeSeconds(tx.CreatedAt); + var expired = !isPaid && expiresAt < DateTimeOffset.UtcNow; + var s = tx.SettledAt is not null + ? DateTimeOffset.FromUnixTimeSeconds(tx.SettledAt.Value) + : (DateTimeOffset?) null; + return new LightningPayment() + { + PaymentHash = tx.PaymentHash, + Amount = LightMoney.MilliSatoshis(tx.AmountMsats), + Preimage = tx.Preimage, + Id = tx.PaymentHash, + Status = isPaid ? LightningPaymentStatus.Complete : + expired ? LightningPaymentStatus.Failed : LightningPaymentStatus.Unknown, + BOLT11 = tx.Invoice, + Fee = LightMoney.MilliSatoshis(tx.FeesPaidMsats), + AmountSent = LightMoney.MilliSatoshis(tx.AmountMsats + tx.FeesPaidMsats), + CreatedAt = created + }; + } + + + public async Task GetPayment(string paymentHash, + CancellationToken cancellation = new CancellationToken()) + { + var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var tx = await client.SendNIP47Request(_connectParams.pubkey, _connectParams.secret, + new NIP47.LookupInvoiceRequest() + { + PaymentHash = paymentHash + }, cancellation); + return ToLightningPayment(tx)!; + } + } + + public async Task ListPayments(CancellationToken cancellation = new CancellationToken()) + { + return await ListPayments(new ListPaymentsParams(), cancellation); + } + + public async Task ListPayments(ListPaymentsParams request, + CancellationToken cancellation = new CancellationToken()) + { + var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var response = await client.SendNIP47Request(_connectParams.pubkey, + _connectParams.secret, + new NIP47.ListTransactionsRequest() + { + Type = "outgoing", + Offset = (int) (request.OffsetIndex ?? 0), + Unpaid = request.IncludePending ?? false, + }, cancellation); + return response.Transactions.Select(ToLightningPayment).Where(i => i is not null).ToArray()!; + } + } + + public async Task CreateInvoice(LightMoney amount, string description, TimeSpan expiry, + CancellationToken cancellation = new CancellationToken()) + { + return await CreateInvoice(new CreateInvoiceParams(amount, description, expiry), cancellation); + } + + public async Task CreateInvoice(CreateInvoiceParams createInvoiceRequest, + CancellationToken cancellation = new CancellationToken()) + { + var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var response = await client.SendNIP47Request(_connectParams.pubkey, + _connectParams.secret, + new NIP47.MakeInvoiceRequest() + { + AmountMsats = createInvoiceRequest.Amount.MilliSatoshi, + Description = createInvoiceRequest.Description is null || createInvoiceRequest.DescriptionHashOnly + ? null + : createInvoiceRequest.Description, + DescriptionHash = createInvoiceRequest.DescriptionHash?.ToString(), + ExpirySeconds = (int) createInvoiceRequest.Expiry.TotalSeconds, + }, cancellation); + return ToLightningInvoice(response, _network)!; + } + } + + public async Task Listen(CancellationToken cancellation = new CancellationToken()) + { + var x = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + if (_commands.Notifications?.Contains("payment_received") is true) + { + return new NotificationListener(_network, x, _connectParams); + } + + return new PollListener(_network, x, _connectParams); + } + + + public class NotificationListener : ILightningInvoiceListener + { + private readonly Network _network; + private readonly INostrClient _client; + + private readonly CancellationTokenSource _cts; + private readonly IAsyncEnumerable _notifications; + private readonly IDisposable _disposable; + + public NotificationListener(Network network, (INostrClient, IDisposable) client, + (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) x) + { + _network = network; + _client = client.Item1; + _disposable = client.Item2; + _cts = new CancellationTokenSource(); + _notifications = _client.SubscribeNip47Notifications(x.pubkey, x.secret, _cts.Token); + } + + public void Dispose() + { + _cts.Cancel(); + _disposable.Dispose(); + } + + public async Task WaitInvoice(CancellationToken cancellation) + { + var enumerator = _notifications.GetAsyncEnumerator(cancellation); + while (await enumerator.MoveNextAsync(cancellation)) + { + if (enumerator.Current.NotificationType == "payment_received") + { + var tx = enumerator.Current.Deserialize(); + return ToLightningInvoice(tx, _network)!; + } + } + + throw new Exception("No notification received"); + } + } + + public class PollListener : ILightningInvoiceListener + { + private readonly Network _network; + private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectparams; + private readonly INostrClient _client; + + private readonly CancellationTokenSource _cts; + private readonly IAsyncEnumerable _notifications; + private readonly IDisposable _disposable; + private NIP47.ListTransactionsResponse? _lastPaid; + private Channel queue = Channel.CreateUnbounded(); + + public PollListener(Network network, (INostrClient, IDisposable) client, + (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) connectparams) + { + _network = network; + _connectparams = connectparams; + _client = client.Item1; + _disposable = client.Item2; + _cts = new CancellationTokenSource(); + _ = Poll(); + } + + + private async Task Poll() + { + try + { + while (!_cts.IsCancellationRequested) + { + var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); + cts.CancelAfter(TimeSpan.FromSeconds(10)); + var paid = await _client.SendNIP47Request(_connectparams.pubkey, + _connectparams.secret, new NIP47.ListTransactionsRequest() + { + Type = "incoming", + // Unpaid = true, //seems like this is ignored... so we only get paid ones + Limit = 300 + }, cancellationToken: cts.Token); + paid.Transactions = paid.Transactions.Where(i => i is {Type: "incoming", SettledAt: not null}).ToArray(); + if (_lastPaid is not null) + { + + var paidInvoicesSinceLastPoll = paid.Transactions.Where(i => + _lastPaid.Transactions.All(j => j.PaymentHash != i.PaymentHash)).Select(i => + ToLightningInvoice(i, _network)!); + + + //all invoices which are no longer in the unpaid list are paid + // var paidInvoicesSinceLastPoll = _lastPaid.Transactions + // .Where(i => paid.Transactions.All(j => j.PaymentHash != i.PaymentHash)) + // .Select(i => ToLightningInvoice(i, _network)!); + foreach (var invoice in paidInvoicesSinceLastPoll) + { + await queue.Writer.WriteAsync(invoice,_cts.Token); + } + } + + _lastPaid = paid; + await Task.Delay(1000, _cts.Token); + } + } + catch (Exception e) + { + Dispose(); + } + } + + public void Dispose() + { + _cts.Cancel(); + _disposable.Dispose(); + queue.Writer.Complete(); + } + + public async Task WaitInvoice(CancellationToken cancellation) + { + return await queue.Reader.ReadAsync(CancellationTokenSource + .CreateLinkedTokenSource(_cts.Token, cancellation).Token); + } + } + + public async Task GetInfo(CancellationToken cancellation = new CancellationToken()) + { + throw new NotSupportedException(); + } + + public async Task GetBalance(CancellationToken cancellation = new CancellationToken()) + { + var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var response = await client.SendNIP47Request(_connectParams.pubkey, + _connectParams.secret, + new NIP47.NIP47Request("get_balance"), cancellation); + return new LightningNodeBalance() + { + OffchainBalance = new OffchainBalance() + { + Local = LightMoney.MilliSatoshis(response.BalanceMsats), + } + }; + } + } + + public async Task Pay(PayInvoiceParams payParams, + CancellationToken cancellation = new CancellationToken()) + { + return await Pay(null, new PayInvoiceParams(), cancellation); + } + + public async Task Pay(string bolt11, PayInvoiceParams payParams, + CancellationToken cancellation = new CancellationToken()) + { + try + { + var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + + using (usage) + { + var response = await client.SendNIP47Request(_connectParams.pubkey, + _connectParams.secret, + bolt11 is null + ? new NIP47.PayKeysendRequest() + { + Amount = Convert.ToDecimal(payParams.Amount.MilliSatoshi), + Pubkey = payParams.Destination.ToHex(), + TlvRecords = payParams.CustomRecords?.Select(kv => new NIP47.TlvRecord() + { + Type = kv.Key.ToString(), + Value = kv.Value + }).ToArray() + } + : new NIP47.PayInvoiceRequest() + { + Invoice = bolt11, + Amount = payParams.Amount?.MilliSatoshi is not null + ? Convert.ToDecimal(payParams.Amount.MilliSatoshi) + : null, + }, cancellation); + var lp = ToLightningPayment(response); + return new PayResponse(lp.Status == LightningPaymentStatus.Complete ? PayResult.Ok : PayResult.Error, + new PayDetails() + { + Preimage = lp.Preimage is null ? null : new uint256(lp.Preimage), + Status = lp.Status, + TotalAmount = lp.AmountSent, + PaymentHash = new uint256(lp.PaymentHash), + FeeAmount = lp.Fee + }); + } + } + catch (Exception e) + { + return new PayResponse() + { + Result = PayResult.Error, + ErrorDetail = e.Message + }; + } + } + + public async Task Pay(string bolt11, CancellationToken cancellation = new CancellationToken()) + { + return await Pay(bolt11, new PayInvoiceParams(), cancellation); + } + + public async Task OpenChannel(OpenChannelRequest openChannelRequest, + CancellationToken cancellation = new CancellationToken()) + { + throw new NotSupportedException(); + } + + public async Task GetDepositAddress(CancellationToken cancellation = new CancellationToken()) + { + throw new NotSupportedException(); + } + + public async Task ConnectTo(NodeInfo nodeInfo, + CancellationToken cancellation = new CancellationToken()) + { + throw new NotSupportedException(); + } + + public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = new CancellationToken()) + { + throw new NotSupportedException(); + } + + public async Task ListChannels(CancellationToken cancellation = new CancellationToken()) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs new file mode 100644 index 0000000..6f8735c --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs @@ -0,0 +1,67 @@ +#nullable enable +using System; +using System.Linq; +using System.Threading; +using BTCPayServer.Lightning; +using NBitcoin; +using NNostr.Client.Protocols; + +namespace BTCPayServer.Plugins.NIP05; + +public class NostrWalletConnectLightningConnectionStringHandler : ILightningConnectionStringHandler +{ + + public ILightningClient? Create(string connectionString, Network network, out string? error) + { + + + if (!connectionString.StartsWith(NIP47.UriScheme, StringComparison.OrdinalIgnoreCase) && !connectionString.StartsWith("type=nwc;key=")) + { + error = null; + return null; + } + + connectionString = connectionString.Replace("type=nwc;key=", ""); + try + { + Uri.TryCreate(connectionString, UriKind.Absolute, out var uri); + var connectParams = NIP47.ParseUri(uri); var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(10)); + var (client, disposable) = NostrClientPool.GetClientAndConnect(connectionString, cts.Token).ConfigureAwait(false).GetAwaiter().GetResult(); + using (disposable) + { + var commands = client.FetchNIP47AvailableCommands(connectParams.Item1, cancellationToken: cts.Token) + .ConfigureAwait(false).GetAwaiter().GetResult(); + var requiredCommands = new[] {"get_info", "make_invoice", "lookup_invoice", "list_transactions"}; + if (commands?.Commands is null || requiredCommands.Any(c => !commands.Value.Commands.Contains(c))) + { + error = + "No commands available or not all required commands are available (get_info, make_invoice, lookup_invoice, list_transactions)"; + return null; + } + + var response = client + .SendNIP47Request(connectParams.pubkey, connectParams.secret, + new NIP47.GetInfoRequest(), cancellationToken: cts.Token).ConfigureAwait(false).GetAwaiter() + .GetResult(); + + var walletNetwork = response.Network; + if (!network.ChainName.ToString().Equals(walletNetwork, + StringComparison.InvariantCultureIgnoreCase)) + { + error = + $"The network of the wallet ({walletNetwork}) does not match the network of the server ({network.ChainName})"; + return null; + } + + error = null; + return new NostrWalletConnectLightningClient(uri, network, commands.Value); + } + } + catch (Exception e) + { + error = "Invalid nostr wallet connect uri"; + return null; + } + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.NIP05/Views/Shared/NWC/LNPaymentMethodSetupTab.cshtml b/Plugins/BTCPayServer.Plugins.NIP05/Views/Shared/NWC/LNPaymentMethodSetupTab.cshtml new file mode 100644 index 0000000..a979d65 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.NIP05/Views/Shared/NWC/LNPaymentMethodSetupTab.cshtml @@ -0,0 +1,41 @@ +@model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel +@{ + var storeId = Model.StoreId; + if (Model.CryptoCode != "BTC") + { + return; + } +} + + + + \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj index 5702773..75fe703 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj @@ -44,7 +44,7 @@ - + diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs index f2a58fa..3070d5a 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs @@ -59,7 +59,7 @@ public class Nostr { new() {TagIdentifier = EndpointTagIdentifier, Data = new List() {new Uri(coordinatorUri, "plugins/wabisabi-coordinator/").ToString()}}, new() {TagIdentifier = TypeTagIdentifier, Data = new List() { TypeTagValue}}, - new() {TagIdentifier = NetworkTagIdentifier, Data = new List() {currentNetwork.Name.ToLower()}} + new() {TagIdentifier = NetworkTagIdentifier, Data = new List() {currentNetwork.ChainName.ToString().ToLower()}} } }; @@ -83,12 +83,13 @@ public class Nostr } }); var result = new List(); - var network = currentNetwork.Name.ToLower(); + var network = currentNetwork.ChainName.ToString().ToLower(); - var cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token, + var cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token, cancellationToken); await nostrClient.Connect(cts.Token); + result = await nostrClient.SubscribeForEvents( new[] { @@ -98,7 +99,7 @@ public class Nostr ExtensionData = new Dictionary() { ["#type"] = JsonSerializer.SerializeToElement(new[] {TypeTagValue}), - ["#network"] = JsonSerializer.SerializeToElement(new[] {network}) + ["#network"] = JsonSerializer.SerializeToElement(new[] {network, currentNetwork.Name.ToLower()}) }, Limit = 1000 } diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 4ebe468..3fbc717 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 4ebe46830b3293b533fd85be984160112e637890 +Subproject commit 3fbc717cd45e2d4a6766715163b1cea50db72f72