nip5 improve

This commit is contained in:
Kukks
2024-06-06 13:13:29 +02:00
parent 72dd7aa1be
commit 01ba71f419
8 changed files with 59 additions and 116 deletions

View File

@@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Nostr</Product> <Product>Nostr</Product>
<Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description> <Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description>
<Version>1.1.9</Version> <Version>1.1.10</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -4,6 +4,8 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services; using BTCPayServer.Abstractions.Services;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NNostr.Client;
namespace BTCPayServer.Plugins.NIP05 namespace BTCPayServer.Plugins.NIP05
{ {
@@ -22,6 +24,7 @@ namespace BTCPayServer.Plugins.NIP05
applicationBuilder.AddSingleton<IPluginHookFilter, LnurlDescriptionFilter>(); applicationBuilder.AddSingleton<IPluginHookFilter, LnurlDescriptionFilter>();
applicationBuilder.AddSingleton<IPluginHookFilter, LnurlFilter>(); applicationBuilder.AddSingleton<IPluginHookFilter, LnurlFilter>();
applicationBuilder.TryAddSingleton<NostrClientPool>();
applicationBuilder.AddSingleton<Zapper>(); applicationBuilder.AddSingleton<Zapper>();
applicationBuilder.AddHostedService(sp => sp.GetRequiredService<Zapper>()); applicationBuilder.AddHostedService(sp => sp.GetRequiredService<Zapper>());
applicationBuilder.AddSingleton<NostrWalletConnectLightningConnectionStringHandler>(); applicationBuilder.AddSingleton<NostrWalletConnectLightningConnectionStringHandler>();

View File

@@ -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<string, NostrClientWrapper> _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();
}
}
}

View File

@@ -16,14 +16,16 @@ namespace BTCPayServer.Plugins.NIP05;
public class NostrWalletConnectLightningClient : ILightningClient public class NostrWalletConnectLightningClient : ILightningClient
{ {
private readonly NostrClientPool _nostrClientPool;
private readonly Uri _uri; private readonly Uri _uri;
private readonly Network _network; private readonly Network _network;
private readonly (string[] Commands, string[] Notifications) _commands; private readonly (string[] Commands, string[] Notifications) _commands;
private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectParams; 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) (string[] Commands, string[] Notifications) commands)
{ {
_nostrClientPool = nostrClientPool;
_uri = uri; _uri = uri;
_network = network; _network = network;
_commands = commands; _commands = commands;
@@ -77,7 +79,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, public async Task<LightningInvoice> GetInvoice(uint256 paymentHash,
CancellationToken cancellation = new CancellationToken()) CancellationToken cancellation = new CancellationToken())
{ {
var (nostrClient, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); var (nostrClient, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
using (usage) using (usage)
{ {
@@ -99,7 +101,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request, public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request,
CancellationToken cancellation = new CancellationToken()) CancellationToken cancellation = new CancellationToken())
{ {
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
using (usage) using (usage)
{ {
@@ -154,7 +156,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<LightningPayment> GetPayment(string paymentHash, public async Task<LightningPayment> GetPayment(string paymentHash,
CancellationToken cancellation = new CancellationToken()) CancellationToken cancellation = new CancellationToken())
{ {
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
using (usage) using (usage)
{ {
@@ -175,7 +177,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request, public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request,
CancellationToken cancellation = new CancellationToken()) CancellationToken cancellation = new CancellationToken())
{ {
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
using (usage) using (usage)
{ {
@@ -200,7 +202,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest, public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest,
CancellationToken cancellation = new CancellationToken()) CancellationToken cancellation = new CancellationToken())
{ {
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
using (usage) using (usage)
{ {
@@ -221,7 +223,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = new CancellationToken()) public async Task<ILightningInvoiceListener> 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) if (_commands.Notifications?.Contains("payment_received") is true)
{ {
return new NotificationListener(_network, x, _connectParams); return new NotificationListener(_network, x, _connectParams);
@@ -361,7 +363,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
public async Task<LightningNodeBalance> GetBalance(CancellationToken cancellation = new CancellationToken()) public async Task<LightningNodeBalance> 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) using (usage)
{ {
@@ -389,7 +391,7 @@ public class NostrWalletConnectLightningClient : ILightningClient
{ {
try try
{ {
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation); var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
using (usage) using (usage)
{ {

View File

@@ -4,13 +4,19 @@ using System.Linq;
using System.Threading; using System.Threading;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using NBitcoin; using NBitcoin;
using NNostr.Client;
using NNostr.Client.Protocols; using NNostr.Client.Protocols;
namespace BTCPayServer.Plugins.NIP05; namespace BTCPayServer.Plugins.NIP05;
public class NostrWalletConnectLightningConnectionStringHandler : ILightningConnectionStringHandler public class NostrWalletConnectLightningConnectionStringHandler : ILightningConnectionStringHandler
{ {
private readonly NostrClientPool _nostrClientPool;
public NostrWalletConnectLightningConnectionStringHandler(NostrClientPool nostrClientPool)
{
_nostrClientPool = nostrClientPool;
}
public ILightningClient? Create(string connectionString, Network network, out string? error) 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); Uri.TryCreate(connectionString, UriKind.Absolute, out var uri);
var connectParams = NIP47.ParseUri(uri); var cts = new CancellationTokenSource(); var connectParams = NIP47.ParseUri(uri); var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10)); 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) using (disposable)
{ {
var commands = client.FetchNIP47AvailableCommands(connectParams.Item1, cancellationToken: cts.Token) var commands = client.FetchNIP47AvailableCommands(connectParams.Item1, cancellationToken: cts.Token)
@@ -55,7 +61,7 @@ public class NostrWalletConnectLightningConnectionStringHandler : ILightningConn
} }
error = null; error = null;
return new NostrWalletConnectLightningClient(uri, network, commands.Value); return new NostrWalletConnectLightningClient(_nostrClientPool, uri, network, commands.Value);
} }
} }
catch (Exception e) catch (Exception e)

View File

@@ -15,33 +15,11 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using NBitcoin; using NBitcoin;
using NBitcoin.Secp256k1;
using Newtonsoft.Json;
using NNostr.Client; using NNostr.Client;
using JsonSerializer = System.Text.Json.JsonSerializer; using JsonSerializer = System.Text.Json.JsonSerializer;
namespace BTCPayServer.Plugins.NIP05; 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 public class Zapper : IHostedService
{ {
record PendingZapEvent(string[] relays, NostrEvent nostrEvent); record PendingZapEvent(string[] relays, NostrEvent nostrEvent);
@@ -54,7 +32,7 @@ public class Zapper : IHostedService
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
private IEventAggregatorSubscription _subscription; private IEventAggregatorSubscription _subscription;
private readonly ConcurrentBag<PendingZapEvent> _pendingZapEvents = new(); private readonly ConcurrentBag<PendingZapEvent> _pendingZapEvents = new();
private readonly NNostr.Client.NostrClientPool _nostrClientPool; private readonly NostrClientPool _nostrClientPool;
public async Task<ZapperSettings> GetSettings() public async Task<ZapperSettings> GetSettings()
{ {
@@ -78,7 +56,8 @@ public class Zapper : IHostedService
IMemoryCache memoryCache, IMemoryCache memoryCache,
ILogger<Zapper> logger, ILogger<Zapper> logger,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
InvoiceRepository invoiceRepository) InvoiceRepository invoiceRepository,
NostrClientPool nostrClientPool)
{ {
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_nip5Controller = nip5Controller; _nip5Controller = nip5Controller;
@@ -86,7 +65,7 @@ public class Zapper : IHostedService
_logger = logger; _logger = logger;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_nostrClientPool = new NNostr.Client.NostrClientPool(); _nostrClientPool = nostrClientPool;
} }
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)

View File

@@ -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; }
}

View File

@@ -1,6 +1,10 @@
# BTCPay Server NIP05 Support # 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 ## Usage