diff --git a/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj b/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj index e9f35c5..ec82866 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj +++ b/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj @@ -11,7 +11,7 @@ Nostr NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support - 1.1.9 + 1.1.10 true diff --git a/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs b/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs index 8b317d7..fdb5492 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs +++ b/Plugins/BTCPayServer.Plugins.NIP05/Nip05Plugin.cs @@ -4,6 +4,8 @@ using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Services; using BTCPayServer.Lightning; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NNostr.Client; namespace BTCPayServer.Plugins.NIP05 { @@ -22,6 +24,7 @@ namespace BTCPayServer.Plugins.NIP05 applicationBuilder.AddSingleton(); applicationBuilder.AddSingleton(); + applicationBuilder.TryAddSingleton(); applicationBuilder.AddSingleton(); applicationBuilder.AddHostedService(sp => sp.GetRequiredService()); applicationBuilder.AddSingleton(); diff --git a/Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs deleted file mode 100644 index 1e66750..0000000 --- a/Plugins/BTCPayServer.Plugins.NIP05/NostrClientPool.cs +++ /dev/null @@ -1,77 +0,0 @@ -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/NostrWalletConnectLightningClient.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs index c71e4e3..364b63e 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs +++ b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs @@ -16,14 +16,16 @@ namespace BTCPayServer.Plugins.NIP05; public class NostrWalletConnectLightningClient : ILightningClient { + private readonly NostrClientPool _nostrClientPool; 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, + public NostrWalletConnectLightningClient(NostrClientPool nostrClientPool, Uri uri, Network network, (string[] Commands, string[] Notifications) commands) { + _nostrClientPool = nostrClientPool; _uri = uri; _network = network; _commands = commands; @@ -77,7 +79,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task GetInvoice(uint256 paymentHash, CancellationToken cancellation = new CancellationToken()) { - var (nostrClient, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (nostrClient, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { @@ -99,7 +101,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task ListInvoices(ListInvoicesParams request, CancellationToken cancellation = new CancellationToken()) { - var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { @@ -154,7 +156,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task GetPayment(string paymentHash, CancellationToken cancellation = new CancellationToken()) { - var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { @@ -175,7 +177,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task ListPayments(ListPaymentsParams request, CancellationToken cancellation = new CancellationToken()) { - var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { @@ -200,7 +202,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task CreateInvoice(CreateInvoiceParams createInvoiceRequest, CancellationToken cancellation = new CancellationToken()) { - var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { @@ -221,7 +223,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task Listen(CancellationToken cancellation = new CancellationToken()) { - var x = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var x = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); if (_commands.Notifications?.Contains("payment_received") is true) { return new NotificationListener(_network, x, _connectParams); @@ -361,7 +363,7 @@ public class NostrWalletConnectLightningClient : ILightningClient public async Task GetBalance(CancellationToken cancellation = new CancellationToken()) { - var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { @@ -389,7 +391,7 @@ public class NostrWalletConnectLightningClient : ILightningClient { try { - var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); + var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); using (usage) { diff --git a/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs index 6f8735c..b0caf6c 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs +++ b/Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs @@ -4,13 +4,19 @@ using System.Linq; using System.Threading; using BTCPayServer.Lightning; using NBitcoin; +using NNostr.Client; using NNostr.Client.Protocols; namespace BTCPayServer.Plugins.NIP05; public class NostrWalletConnectLightningConnectionStringHandler : ILightningConnectionStringHandler { - + private readonly NostrClientPool _nostrClientPool; + + public NostrWalletConnectLightningConnectionStringHandler(NostrClientPool nostrClientPool) + { + _nostrClientPool = nostrClientPool; + } public ILightningClient? Create(string connectionString, Network network, out string? error) { @@ -27,7 +33,7 @@ public class NostrWalletConnectLightningConnectionStringHandler : ILightningConn 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(); + var (client, disposable) = _nostrClientPool.GetClientAndConnect(connectParams.relays, cts.Token).ConfigureAwait(false).GetAwaiter().GetResult(); using (disposable) { var commands = client.FetchNIP47AvailableCommands(connectParams.Item1, cancellationToken: cts.Token) @@ -55,7 +61,7 @@ public class NostrWalletConnectLightningConnectionStringHandler : ILightningConn } error = null; - return new NostrWalletConnectLightningClient(uri, network, commands.Value); + return new NostrWalletConnectLightningClient(_nostrClientPool, uri, network, commands.Value); } } catch (Exception e) diff --git a/Plugins/BTCPayServer.Plugins.NIP05/Zapper.cs b/Plugins/BTCPayServer.Plugins.NIP05/Zapper.cs index 2233864..4c6a759 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/Zapper.cs +++ b/Plugins/BTCPayServer.Plugins.NIP05/Zapper.cs @@ -15,33 +15,11 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using NBitcoin; -using NBitcoin.Secp256k1; -using Newtonsoft.Json; using NNostr.Client; using JsonSerializer = System.Text.Json.JsonSerializer; namespace BTCPayServer.Plugins.NIP05; -public class ZapperSettings -{ - public ZapperSettings(string ZapperPrivateKey) - { - this.ZapperPrivateKey = ZapperPrivateKey; - } - - public ZapperSettings() - { - - } - - [JsonIgnore] - public ECPrivKey ZappingKey => NostrExtensions.ParseKey(ZapperPrivateKey); - [JsonIgnore] - public ECXOnlyPubKey ZappingPublicKey => ZappingKey.CreateXOnlyPubKey(); - [JsonIgnore] - public string ZappingPublicKeyHex => ZappingPublicKey.ToHex(); - public string ZapperPrivateKey { get; set; } -} public class Zapper : IHostedService { record PendingZapEvent(string[] relays, NostrEvent nostrEvent); @@ -54,7 +32,7 @@ public class Zapper : IHostedService private readonly InvoiceRepository _invoiceRepository; private IEventAggregatorSubscription _subscription; private readonly ConcurrentBag _pendingZapEvents = new(); - private readonly NNostr.Client.NostrClientPool _nostrClientPool; + private readonly NostrClientPool _nostrClientPool; public async Task GetSettings() { @@ -78,7 +56,8 @@ public class Zapper : IHostedService IMemoryCache memoryCache, ILogger logger, SettingsRepository settingsRepository, - InvoiceRepository invoiceRepository) + InvoiceRepository invoiceRepository, + NostrClientPool nostrClientPool) { _eventAggregator = eventAggregator; _nip5Controller = nip5Controller; @@ -86,7 +65,7 @@ public class Zapper : IHostedService _logger = logger; _settingsRepository = settingsRepository; _invoiceRepository = invoiceRepository; - _nostrClientPool = new NNostr.Client.NostrClientPool(); + _nostrClientPool = nostrClientPool; } public Task StartAsync(CancellationToken cancellationToken) diff --git a/Plugins/BTCPayServer.Plugins.NIP05/ZapperSettings.cs b/Plugins/BTCPayServer.Plugins.NIP05/ZapperSettings.cs new file mode 100644 index 0000000..99f2fae --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.NIP05/ZapperSettings.cs @@ -0,0 +1,26 @@ +using NBitcoin.Secp256k1; +using Newtonsoft.Json; +using NNostr.Client; + +namespace BTCPayServer.Plugins.NIP05; + +public class ZapperSettings +{ + public ZapperSettings(string ZapperPrivateKey) + { + this.ZapperPrivateKey = ZapperPrivateKey; + } + + public ZapperSettings() + { + + } + + [JsonIgnore] + public ECPrivKey ZappingKey => NostrExtensions.ParseKey(ZapperPrivateKey); + [JsonIgnore] + public ECXOnlyPubKey ZappingPublicKey => ZappingKey.CreateXOnlyPubKey(); + [JsonIgnore] + public string ZappingPublicKeyHex => ZappingPublicKey.ToHex(); + public string ZapperPrivateKey { get; set; } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.NIP05/readme.md b/Plugins/BTCPayServer.Plugins.NIP05/readme.md index 703cd2e..2d69496 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/readme.md +++ b/Plugins/BTCPayServer.Plugins.NIP05/readme.md @@ -1,6 +1,10 @@ # BTCPay Server NIP05 Support -This plugin allows your BTCPay Server to support the [Nostr](https://github.com/nostr-protocol/nostr)[ NIP05 protocol](https://github.com/nostr-protocol/nips/blob/master/05.md) to verify accounts. +This plugin allows your BTCPay Server to support + +* [Nostr](https://github.com/nostr-protocol/nostr)[ NIP05 protocol](https://github.com/nostr-protocol/nips/blob/master/05.md) to verify accounts. +* [Nostr](https://github.com/nostr-protocol/nostr)[ NIP57 protocol](https://github.com/nostr-protocol/nips/blob/master/57.md) to support Zaps. +* [Nostr](https://github.com/nostr-protocol/nostr)[ NIP47 protocol](https://github.com/nostr-protocol/nips/blob/master/47.md) to accept payments to your NWC enabled lightning wallet. ## Usage