Repository navigation
NWC: use nip44 encryption — fixes missing payment notifications with nip44-only wallets #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |||||||||||||||||||||||||||
| using BTCPayServer.Payments.Lightning; | ||||||||||||||||||||||||||||
| using NBitcoin; | ||||||||||||||||||||||||||||
| using NBitcoin.Secp256k1; | ||||||||||||||||||||||||||||
| using Microsoft.Extensions.Logging; | ||||||||||||||||||||||||||||
| using NNostr.Client; | ||||||||||||||||||||||||||||
| using NNostr.Client.Protocols; | ||||||||||||||||||||||||||||
| using SHA256 = System.Security.Cryptography.SHA256; | ||||||||||||||||||||||||||||
|
|
@@ -32,11 +33,15 @@ public class NostrWalletConnectLightningClient : IExtendedLightningClient | |||||||||||||||||||||||||||
| private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectParams; | ||||||||||||||||||||||||||||
| private readonly Uri? _serverUri; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public NostrWalletConnectLightningClient(NostrClientPool nostrClientPool, Uri uri, Network network) | ||||||||||||||||||||||||||||
| private readonly ILogger<NostrWalletConnectLightningClient>? _logger; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public NostrWalletConnectLightningClient(NostrClientPool nostrClientPool, Uri uri, Network network, | ||||||||||||||||||||||||||||
| ILogger<NostrWalletConnectLightningClient>? logger = null) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| _nostrClientPool = nostrClientPool; | ||||||||||||||||||||||||||||
| _uri = uri; | ||||||||||||||||||||||||||||
| _network = network; | ||||||||||||||||||||||||||||
| _logger = logger; | ||||||||||||||||||||||||||||
| _connectParams = NIP47.ParseUri(uri); | ||||||||||||||||||||||||||||
| _serverUri = _connectParams.relays.FirstOrDefault(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -46,6 +51,25 @@ public override string ToString() | |||||||||||||||||||||||||||
| return $"type=nwc;key={_uri}"; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private NIP47.EncryptionScheme? _encryptionScheme; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // NIP-47: the wallet service advertises supported encryption schemes via the "encryption" | ||||||||||||||||||||||||||||
| // tag on its kind-13194 info event. Prefer nip44_v2 when available, otherwise fall back | ||||||||||||||||||||||||||||
| // to nip04 (absence of the tag means the wallet only supports nip04). | ||||||||||||||||||||||||||||
| private async Task<NIP47.EncryptionScheme> GetEncryptionScheme(INostrClient client, CancellationToken cancellationToken) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| if (_encryptionScheme is { } cached) | ||||||||||||||||||||||||||||
| return cached; | ||||||||||||||||||||||||||||
| var info = await client.FetchNIP47InfoEvent(_connectParams.pubkey, cancellationToken); | ||||||||||||||||||||||||||||
| var scheme = info?.EncryptionSchemes?.Contains(NIP47.EncryptionSchemeNip44V2) is true | ||||||||||||||||||||||||||||
| ? NIP47.EncryptionScheme.Nip44V2 | ||||||||||||||||||||||||||||
| : NIP47.EncryptionScheme.Nip04; | ||||||||||||||||||||||||||||
| _encryptionScheme = scheme; | ||||||||||||||||||||||||||||
| _logger?.LogInformation("NWC: negotiated {Scheme} encryption with wallet service on {Relay} (advertised: {Advertised})", | ||||||||||||||||||||||||||||
| scheme, _serverUri, info?.EncryptionSchemes is { Length: > 0 } s ? string.Join(" ", s) : "(no encryption tag)"); | ||||||||||||||||||||||||||||
| return scheme; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public async Task<LightningInvoice> GetInvoice(string invoiceId, | ||||||||||||||||||||||||||||
| CancellationToken cancellation = new()) | ||||||||||||||||||||||||||||
|
|
@@ -94,12 +118,13 @@ public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| using (usage) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(nostrClient, cts.Token); | ||||||||||||||||||||||||||||
| var tx = await nostrClient.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey, | ||||||||||||||||||||||||||||
| _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.LookupInvoiceRequest() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| PaymentHash = paymentHash.ToString() | ||||||||||||||||||||||||||||
| }, cts.Token); | ||||||||||||||||||||||||||||
| }, cts.Token, scheme); | ||||||||||||||||||||||||||||
| return ToLightningInvoice(tx, _network)!; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -118,14 +143,15 @@ public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request, | |||||||||||||||||||||||||||
| var (client, usage) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cts.Token); | ||||||||||||||||||||||||||||
| using (usage) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(client, cts.Token); | ||||||||||||||||||||||||||||
| var response = await client.SendNIP47Request<NIP47.ListTransactionsResponse>(_connectParams.pubkey, | ||||||||||||||||||||||||||||
| _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.ListTransactionsRequest() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| Type = "incoming", | ||||||||||||||||||||||||||||
| Offset = (int)(request.OffsetIndex ?? 0), | ||||||||||||||||||||||||||||
| Unpaid = request.PendingOnly ?? false, | ||||||||||||||||||||||||||||
| }, cts.Token); | ||||||||||||||||||||||||||||
| }, cts.Token, scheme); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return response.Transactions.Select(transaction => ToLightningInvoice(transaction, _network)) | ||||||||||||||||||||||||||||
| .Where(i => i is not null).ToArray()!; | ||||||||||||||||||||||||||||
|
|
@@ -177,11 +203,12 @@ public async Task<LightningPayment> GetPayment(string paymentHash, | |||||||||||||||||||||||||||
| NIP47.Nip47Transaction tx; | ||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(nostrClient, cts.Token); | ||||||||||||||||||||||||||||
| tx = await nostrClient.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey, _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.LookupInvoiceRequest() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| PaymentHash = paymentHash | ||||||||||||||||||||||||||||
| }, cts.Token); | ||||||||||||||||||||||||||||
| }, cts.Token, scheme); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| // The standard says it returns NOT_FOUND error, but | ||||||||||||||||||||||||||||
| // Alby returns INTERNAL error... Probably safer to catch all | ||||||||||||||||||||||||||||
|
|
@@ -207,14 +234,15 @@ public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request, | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| using (usage) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(nostrClient, cts.Token); | ||||||||||||||||||||||||||||
| var response = await nostrClient.SendNIP47Request<NIP47.ListTransactionsResponse>(_connectParams.pubkey, | ||||||||||||||||||||||||||||
| _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.ListTransactionsRequest() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| Type = "outgoing", | ||||||||||||||||||||||||||||
| Offset = (int)(request.OffsetIndex ?? 0), | ||||||||||||||||||||||||||||
| Unpaid = request.IncludePending ?? false, | ||||||||||||||||||||||||||||
| }, cts.Token); | ||||||||||||||||||||||||||||
| }, cts.Token, scheme); | ||||||||||||||||||||||||||||
| return response.Transactions.Select(ToLightningPayment).Where(i => i is not null).ToArray()!; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -235,6 +263,7 @@ public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvo | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| using (usage) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(nostrClient, cts.Token); | ||||||||||||||||||||||||||||
| var response = await nostrClient.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey, | ||||||||||||||||||||||||||||
| _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.MakeInvoiceRequest() | ||||||||||||||||||||||||||||
|
|
@@ -245,24 +274,30 @@ public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvo | |||||||||||||||||||||||||||
| : createInvoiceRequest.Description, | ||||||||||||||||||||||||||||
| DescriptionHash = createInvoiceRequest.DescriptionHash?.ToString(), | ||||||||||||||||||||||||||||
| ExpirySeconds = (int)createInvoiceRequest.Expiry.TotalSeconds, | ||||||||||||||||||||||||||||
| }, cts.Token); | ||||||||||||||||||||||||||||
| }, cts.Token, scheme); | ||||||||||||||||||||||||||||
| return ToLightningInvoice(response, _network)!; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = default) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var x = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); | ||||||||||||||||||||||||||||
| var commands = await x.Item1.FetchNIP47AvailableCommands(_connectParams.Item1, cancellationToken: cancellation); | ||||||||||||||||||||||||||||
| bool? hasNotification = commands?.Notifications?.Contains("payment_received"); | ||||||||||||||||||||||||||||
| var info = await x.Item1.FetchNIP47InfoEvent(_connectParams.Item1, cancellationToken: cancellation); | ||||||||||||||||||||||||||||
| var scheme = info?.EncryptionSchemes?.Contains(NIP47.EncryptionSchemeNip44V2) is true | ||||||||||||||||||||||||||||
| ? NIP47.EncryptionScheme.Nip44V2 | ||||||||||||||||||||||||||||
| : NIP47.EncryptionScheme.Nip04; | ||||||||||||||||||||||||||||
| _encryptionScheme = scheme; | ||||||||||||||||||||||||||||
| bool? hasNotification = info?.Notifications?.Contains("payment_received"); | ||||||||||||||||||||||||||||
| if (hasNotification is false) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var response = await x.Item1.SendNIP47Request<NIP47.GetInfoResponse>(_connectParams.pubkey, _connectParams.secret, new NIP47.GetInfoRequest(), cancellationToken: cancellation); | ||||||||||||||||||||||||||||
| var response = await x.Item1.SendNIP47Request<NIP47.GetInfoResponse>(_connectParams.pubkey, _connectParams.secret, new NIP47.GetInfoRequest(), cancellationToken: cancellation, encryptionScheme: scheme); | ||||||||||||||||||||||||||||
| hasNotification = response?.Notifications?.Contains("payment_received"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+290
to
295
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Query
Proposed fix- if (hasNotification is false)
+ if (hasNotification is not true)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| _logger?.LogInformation("NWC: listening for paid invoices via {Listener} ({Scheme}, payment_received supported: {HasNotification})", | ||||||||||||||||||||||||||||
| hasNotification is true ? "push notifications" : "polling", scheme, hasNotification ?? false); | ||||||||||||||||||||||||||||
| return hasNotification is true | ||||||||||||||||||||||||||||
| ? new NotificationListener(_network, x, _connectParams) | ||||||||||||||||||||||||||||
| : new PollListener(_network, x, _connectParams); | ||||||||||||||||||||||||||||
| ? new NotificationListener(_network, x, _connectParams, scheme) | ||||||||||||||||||||||||||||
| : new PollListener(_network, x, _connectParams, scheme); | ||||||||||||||||||||||||||||
|
Comment on lines
284
to
+300
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Dispose the connection usage lease if listener initialization fails.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -276,13 +311,14 @@ public class NotificationListener : ILightningInvoiceListener | |||||||||||||||||||||||||||
| private readonly IDisposable _disposable; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public NotificationListener(Network network, (INostrClient, IDisposable) client, | ||||||||||||||||||||||||||||
| (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) x) | ||||||||||||||||||||||||||||
| (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) x, | ||||||||||||||||||||||||||||
| NIP47.EncryptionScheme encryptionScheme) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| _network = network; | ||||||||||||||||||||||||||||
| _client = client.Item1; | ||||||||||||||||||||||||||||
| _disposable = client.Item2; | ||||||||||||||||||||||||||||
| _cts = new CancellationTokenSource(); | ||||||||||||||||||||||||||||
| _notifications = _client.SubscribeNip47Notifications(x.pubkey, x.secret, _cts.Token); | ||||||||||||||||||||||||||||
| _notifications = _client.SubscribeNip47Notifications(x.pubkey, x.secret, _cts.Token, encryptionScheme); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void Dispose() | ||||||||||||||||||||||||||||
|
|
@@ -319,13 +355,17 @@ public class PollListener : ILightningInvoiceListener | |||||||||||||||||||||||||||
| private NIP47.ListTransactionsResponse? _lastPaid; | ||||||||||||||||||||||||||||
| private Channel<LightningInvoice> queue = Channel.CreateUnbounded<LightningInvoice>(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private readonly NIP47.EncryptionScheme _encryptionScheme; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public PollListener(Network network, (INostrClient, IDisposable) client, | ||||||||||||||||||||||||||||
| (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) connectparams) | ||||||||||||||||||||||||||||
| (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) connectparams, | ||||||||||||||||||||||||||||
| NIP47.EncryptionScheme encryptionScheme) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| _network = network; | ||||||||||||||||||||||||||||
| _connectparams = connectparams; | ||||||||||||||||||||||||||||
| _client = client.Item1; | ||||||||||||||||||||||||||||
| _disposable = client.Item2; | ||||||||||||||||||||||||||||
| _encryptionScheme = encryptionScheme; | ||||||||||||||||||||||||||||
| _cts = new CancellationTokenSource(); | ||||||||||||||||||||||||||||
| _ = Poll(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -343,9 +383,9 @@ private async Task Poll() | |||||||||||||||||||||||||||
| _connectparams.secret, new NIP47.ListTransactionsRequest() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| Type = "incoming", | ||||||||||||||||||||||||||||
| // Unpaid = true, //seems like this is ignored... so we only get paid ones | ||||||||||||||||||||||||||||
| // Unpaid = true, //seems like this is ignored... so we only get paid ones | ||||||||||||||||||||||||||||
| Limit = 300 | ||||||||||||||||||||||||||||
| }, cancellationToken: cts.Token); | ||||||||||||||||||||||||||||
| }, cancellationToken: cts.Token, encryptionScheme: _encryptionScheme); | ||||||||||||||||||||||||||||
| paid.Transactions = paid.Transactions.Where(i => i is { Type: "incoming", SettledAt: not null }).ToArray(); | ||||||||||||||||||||||||||||
| if (_lastPaid is not null) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
|
|
@@ -402,9 +442,10 @@ public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation) | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| using (usage) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(client, cts.Token); | ||||||||||||||||||||||||||||
| var response = await client.SendNIP47Request<NIP47.GetBalanceResponse>(_connectParams.pubkey, | ||||||||||||||||||||||||||||
| _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.NIP47Request("get_balance"), cts.Token); | ||||||||||||||||||||||||||||
| new NIP47.NIP47Request("get_balance"), cts.Token, scheme); | ||||||||||||||||||||||||||||
| return new LightningNodeBalance() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| OffchainBalance = new OffchainBalance() | ||||||||||||||||||||||||||||
|
|
@@ -456,7 +497,8 @@ public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams, | |||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| var response = await client.SendNIP47Request<NIP47.PayInvoiceResponse>(_connectParams.pubkey, _connectParams.secret, request, cts.Token); | ||||||||||||||||||||||||||||
| var scheme = await GetEncryptionScheme(client, cts.Token); | ||||||||||||||||||||||||||||
| var response = await client.SendNIP47Request<NIP47.PayInvoiceResponse>(_connectParams.pubkey, _connectParams.secret, request, cts.Token, scheme); | ||||||||||||||||||||||||||||
| var payHash = ConvertHelper.ToHexString(SHA256.HashData(Convert.FromHexString(response.Preimage))); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||
|
|
@@ -465,7 +507,7 @@ public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams, | |||||||||||||||||||||||||||
| new NIP47.LookupInvoiceRequest() | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| PaymentHash = payHash | ||||||||||||||||||||||||||||
| }, cts.Token); | ||||||||||||||||||||||||||||
| }, cts.Token, scheme); | ||||||||||||||||||||||||||||
| var lp = ToLightningPayment(tx)!; | ||||||||||||||||||||||||||||
| return new PayResponse(lp.Status == LightningPaymentStatus.Complete ? PayResult.Ok : PayResult.Error, | ||||||||||||||||||||||||||||
| new PayDetails() | ||||||||||||||||||||||||||||
|
|
@@ -530,16 +572,21 @@ public async Task<ConnectionResult> ConnectTo(NodeInfo nodeInfo, | |||||||||||||||||||||||||||
| var (client, disposable) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cts.Token).ConfigureAwait(false); | ||||||||||||||||||||||||||||
| using (disposable) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| var commands = await client.FetchNIP47AvailableCommands(_connectParams.Item1, cancellationToken: cts.Token); | ||||||||||||||||||||||||||||
| var info = await client.FetchNIP47InfoEvent(_connectParams.Item1, cancellationToken: cts.Token); | ||||||||||||||||||||||||||||
| var requiredCommands = new[] { "get_info", "make_invoice", "lookup_invoice", "list_transactions" }; | ||||||||||||||||||||||||||||
| if (commands?.Commands is null || requiredCommands.Any(c => !commands.Value.Commands.Contains(c))) | ||||||||||||||||||||||||||||
| if (info?.Commands is null || requiredCommands.Any(c => !info.Value.Commands.Contains(c))) | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| return new ValidationResult("No commands available or not all required commands are available (get_info, make_invoice, lookup_invoice, list_transactions)"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| var scheme = info.Value.EncryptionSchemes?.Contains(NIP47.EncryptionSchemeNip44V2) is true | ||||||||||||||||||||||||||||
| ? NIP47.EncryptionScheme.Nip44V2 | ||||||||||||||||||||||||||||
| : NIP47.EncryptionScheme.Nip04; | ||||||||||||||||||||||||||||
| _encryptionScheme = scheme; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| var response = await client | ||||||||||||||||||||||||||||
| .SendNIP47Request<NIP47.GetInfoResponse>(_connectParams.pubkey, _connectParams.secret, | ||||||||||||||||||||||||||||
| new NIP47.GetInfoRequest(), cancellationToken: cts.Token); | ||||||||||||||||||||||||||||
| new NIP47.GetInfoRequest(), cancellationToken: cts.Token, encryptionScheme: scheme); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| var walletNetwork = response.Network; | ||||||||||||||||||||||||||||
| if (!_network.ChainName.ToString().Equals(walletNetwork, | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 163
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 25506
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 214
🌐 Web query:
NNostr.Client NuGet 0.0.54 NIP-44 EncryptionScheme Nip44V2💡 Result:
As of August 12, 2026, the NNostr.Client NuGet package (maintained by Kukks) is at version 0.0.54 [1]. While the project is a widely used C# Nostr client [2][3], there is no explicit documentation or public changelog confirming native implementation or support for NIP-44 (specifically Nip44V2 encryption) within this specific library version [2][4]. NIP-44 is the standard for versioned encrypted payloads in Nostr, utilizing ChaCha20-Poly1305 and HKDF for key derivation, effectively succeeding the older NIP-04 encryption [5][6]. Developers requiring NIP-44 functionality in C# often look toward alternative libraries or manual implementations that follow the NIP-44 specification [5]. If you are implementing NIP-44, the specification requires the use of HKDF-extract and HKDF-expand to derive conversation keys, and the ChaCha20 stream cipher as defined in the official NIP-44 documentation [5]. If you are using NNostr.Client and require NIP-44 support, you should check the official GitHub repository's issue tracker or pull requests, as support for newer NIPs is often added via community contributions [2][4][7]. Users requiring more comprehensive NIP coverage, such as those found in the Nostr.Client library (by Marfusios), may find different levels of protocol support, though you should verify specific NIP-44 support there as well [8][9].
Citations:
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 7293
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 4223
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 42596
🏁 Script executed:
Repository: Kukks/BTCPayServerPlugins
Length of output: 7145
Update the
NNostr.Clientpackage reference before using the new NIP-47 APIs.Version
0.0.54lacksFetchNIP47InfoEvent,NIP47.EncryptionSchemeNip44V2, and theSendNIP47Requestoverload that accepts an encryption scheme. The project will not compile until the reference targets a release that provides these APIs.🤖 Prompt for AI Agents