Repository navigation
NWC: use nip44 encryption — fixes missing payment notifications with nip44-only wallets - #148
Conversation
Reads the encryption schemes the wallet service advertises on its kind-13194 info event and prefers nip44_v2 over the deprecated nip04 (falling back to nip04 when the tag is absent, per NIP-47). The negotiated scheme is cached per client and applied to every NWC request, and to notification subscriptions (kind 23197 for nip44). Requires NNostr.Client with nip44 NWC support.
Makes it observable in the server logs whether a wallet service negotiated nip44_v2 or fell back to nip04, and whether invoice settlement uses push notifications or polling.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe NIP-05 Lightning client negotiates NIP-44 v2 or NIP-04 from wallet information events, caches the selected scheme, and uses it for NIP-47 requests, listeners, polling, and validation. Logging is injected through the connection-string handler. ChangesNIP-47 encryption negotiation
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: 🟡 Moderate · up to The change depends on a newer encryption dependency, but the PR notes that the current reference will not compile until it is updated and validated. Do not merge until the dependency update is applied and both the newer-wallet and legacy-wallet paths pass build and tests. Sequence Diagram(s)sequenceDiagram
participant LightningClient as NostrWalletConnectLightningClient
participant Relay as Nostr relay
participant Wallet as NIP-47 wallet
LightningClient->>Relay: Retrieve wallet info event
Relay-->>LightningClient: Return commands and encryption support
LightningClient->>Wallet: Send encrypted get_info request
Wallet-->>LightningClient: Return wallet information
LightningClient->>LightningClient: Cache NIP-44 v2 or NIP-04
LightningClient->>Wallet: Send NIP-47 request using selected scheme
Wallet-->>LightningClient: Return encrypted response
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs`:
- Around line 284-300: Update the listener initialization flow around
GetClientAndConnect, FetchNIP47InfoEvent, and SendNIP47Request to catch
initialization failures, dispose x.Item2 before rethrowing, and preserve the
existing transfer of the lease to NotificationListener or PollListener on
successful return.
- Around line 290-295: Update the condition in the notification capability check
around hasNotification so the get_info fallback executes whenever
payment_received has not been confirmed, including when the value is null.
Replace the false-only check with an is-not-true check, preserving the existing
request and response handling.
- Around line 63-66: Update the project’s NNostr.Client package reference to a
release that provides FetchNIP47InfoEvent, NIP47.EncryptionSchemeNip44V2, and
the SendNIP47Request overload accepting an encryption scheme, ensuring the
existing NostrWalletConnectLightningClient usage compiles.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1283c7f1-6926-4fa3-8fa2-9f0d6ba40b0c
📒 Files selected for processing (2)
Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.csPlugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningConnectionStringHandler.cs
| var info = await client.FetchNIP47InfoEvent(_connectParams.pubkey, cancellationToken); | ||
| var scheme = info?.EncryptionSchemes?.Contains(NIP47.EncryptionSchemeNip44V2) is true | ||
| ? NIP47.EncryptionScheme.Nip44V2 | ||
| : NIP47.EncryptionScheme.Nip04; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 'NNostr\.Client|PackageReference Include="NNostr\.Client"|PackageVersion Include="NNostr\.Client"' \
-g '*.csproj' -g 'Directory.Packages.props' -g 'packages.props'Repository: Kukks/BTCPayServerPlugins
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- matching project and package files ---'
git ls-files '*.csproj' 'Directory.Packages.props' 'packages.props' | sort
printf '%s\n' '--- NNostr references and API usage ---'
rg -n -i -C 3 'NNostr|Nip44V2|EncryptionSchemeNip44V2|FetchNIP47InfoEvent' \
-g '*.cs' -g '*.csproj' -g '*.props' -g '*.targets' -g '*.sln' .
printf '%s\n' '--- target file context ---'
target=$(git ls-files | grep -F 'NostrWalletConnectLightningClient.cs' | head -n 1 || true)
if [ -n "$target" ]; then
cat -n "$target" | sed -n '1,130p'
else
printf '%s\n' 'Target file not found'
fiRepository: Kukks/BTCPayServerPlugins
Length of output: 25506
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
url='https://api.nuget.org/v3-flatcontainer/nnstr.client/0.0.54/nnstr.client.0.0.54.nupkg'
curl -fsSL "$url" -o "$tmp/package.nupkg"
printf '%s\n' '--- package contents ---'
unzip -l "$tmp/package.nupkg"
printf '%s\n' '--- package metadata ---'
unzip -p "$tmp/package.nupkg" '*.nuspec' | sed -n '1,160p'
printf '%s\n' '--- API symbol strings ---'
unzip -p "$tmp/package.nupkg" 'lib/**/*.xml' 2>/dev/null | \
rg -n 'Nip44|FetchNIP47InfoEvent|EncryptionScheme|SendNIP47Request' || trueRepository: 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:
- 1: https://www.nuget.org/profiles/Kukks
- 2: https://github.com/kukks/Nnostr
- 3: https://github.com/kukks/nnostr
- 4: https://github.com/Kukks/NNostr
- 5: https://nips.nostr.com/44
- 6: https://github.com/block-core/nostr-client/blob/master/NIP44_IMPLEMENTATION.md
- 7: Kukks/NNostr@90c50a2
- 8: https://www.nuget.org/packages/Nostr.Client
- 9: https://github.com/marfusios/nostr-client/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- NuGet package search ---'
curl -fsSLG 'https://azuresearch-usnc.nuget.org/query' \
--data-urlencode 'q=NNostr.Client' \
--data-urlencode 'prerelease=false' |
python3 -c '
import json, sys
data=json.load(sys.stdin)
for item in data.get("data", []):
print(item.get("id"), item.get("version"), item.get("versions"))
'
printf '%s\n' '--- NuGet registration candidates ---'
for id in nnostr.client nnstr.client nn-ostr.client; do
url="https://api.nuget.org/v3-registration5-gz-semver2/${id}/index.json"
printf '%s: ' "$id"
curl -sS -o /tmp/nuget-registration.json -w '%{http_code}\n' "$url"
if [ -s /tmp/nuget-registration.json ]; then
python3 - "$id" <<'PY'
import json, sys
try:
data=json.load(open("/tmp/nuget-registration.json"))
versions=[]
for page in data.get("items", []):
for item in page.get("items", []) or []:
catalog=item.get("catalogEntry", {})
versions.append(catalog.get("version"))
print(", ".join(v for v in versions if v and (v == "0.0.54" or v.startswith("0.0.")))[-500:])
except Exception as e:
print(e)
PY
fi
doneRepository: Kukks/BTCPayServerPlugins
Length of output: 7293
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
url='https://api.nuget.org/v3-flatcontainer/nnostr.client/0.0.54/nnostr.client.0.0.54.nupkg'
curl -fsSL "$url" -o "$tmp/package.nupkg"
printf '%s\n' '--- package contents ---'
unzip -l "$tmp/package.nupkg"
printf '%s\n' '--- package metadata ---'
unzip -p "$tmp/package.nupkg" '*.nuspec' | sed -n '1,160p'
printf '%s\n' '--- API symbol strings in XML docs ---'
unzip -p "$tmp/package.nupkg" 'lib/**/*.xml' 2>/dev/null | \
rg -n 'Nip44|FetchNIP47InfoEvent|EncryptionScheme|SendNIP47Request' || true
printf '%s\n' '--- API symbol strings in binaries ---'
unzip -p "$tmp/package.nupkg" 'lib/**/*.dll' 2>/dev/null | \
strings | rg -n 'Nip44|FetchNIP47InfoEvent|EncryptionScheme|SendNIP47Request' || trueRepository: Kukks/BTCPayServerPlugins
Length of output: 4223
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -fsSL \
'https://github.com/Kukks/NNostr/archive/45dee385b2527bcf8763674fcf3ca283aee71ffd.tar.gz' \
-o "$tmp/source.tar.gz"
tar -xzf "$tmp/source.tar.gz" -C "$tmp"
src=$(find "$tmp" -maxdepth 1 -type d -name 'NNostr-*' | head -n 1)
printf '%s\n' '--- source revision ---'
rg -n -i -C 3 'Nip44|FetchNIP47InfoEvent|EncryptionScheme|SendNIP47Request' "$src" \
-g '*.cs' -g '*.csproj' -g '*.md' || true
printf '%s\n' '--- NIP-47 and encryption declarations ---'
rg -n -i -C 5 'class NIP47|enum EncryptionScheme|EncryptionSchemeNip04|EncryptionSchemeNip44V2|SendNIP47Request' "$src" \
-g '*.cs' || trueRepository: Kukks/BTCPayServerPlugins
Length of output: 42596
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -fsSL \
'https://github.com/Kukks/NNostr/archive/45dee385b2527bcf8763674fcf3ca283aee71ffd.tar.gz' \
-o "$tmp/source.tar.gz"
tar -xzf "$tmp/source.tar.gz" -C "$tmp"
src=$(find "$tmp" -maxdepth 1 -type d -name 'NNostr-*' | head -n 1)
printf '%s\n' '--- NIP-47 implementation in the 0.0.54 source revision ---'
sed -n '110,210p' "$src/NNostr.Client/Protocols/NIP47.cs"
printf '%s\n' '--- all requested symbols ---'
rg -n -i 'FetchNIP47InfoEvent|EncryptionSchemes|EncryptionSchemeNip44V2|enum EncryptionScheme|SendNIP47Request' \
"$src/NNostr.Client" -g '*.cs' || true
printf '%s\n' '--- package reference locations ---'
rg -n -C 2 'PackageReference Include="NNostr\.Client"' \
-g '*.csproj' -g '*.props' -g '*.targets' .Repository: Kukks/BTCPayServerPlugins
Length of output: 7145
Update the NNostr.Client package reference before using the new NIP-47 APIs.
Version 0.0.54 lacks FetchNIP47InfoEvent, NIP47.EncryptionSchemeNip44V2, and the SendNIP47Request overload that accepts an encryption scheme. The project will not compile until the reference targets a release that provides these APIs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs`
around lines 63 - 66, Update the project’s NNostr.Client package reference to a
release that provides FetchNIP47InfoEvent, NIP47.EncryptionSchemeNip44V2, and
the SendNIP47Request overload accepting an encryption scheme, ensuring the
existing NostrWalletConnectLightningClient usage compiles.
| 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"); | ||
| } | ||
| _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); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Dispose the connection usage lease if listener initialization fails.
x.Item2 is transferred to a listener only on the return path. If FetchNIP47InfoEvent or SendNIP47Request throws first, the lease is not disposed. Dispose x.Item2 in a catch block before rethrowing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs`
around lines 284 - 300, Update the listener initialization flow around
GetClientAndConnect, FetchNIP47InfoEvent, and SendNIP47Request to catch
initialization failures, dispose x.Item2 before rethrowing, and preserve the
existing transfer of the lease to NotificationListener or PollListener on
successful return.
| 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"); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Query get_info when the info event has no notification capability.
hasNotification is null when the info event or its Notifications field is absent. The current is false condition skips the encrypted get_info fallback and selects polling even when the wallet advertises payment_received through get_info. Use is not true.
Proposed fix
- if (hasNotification is false)
+ if (hasNotification is not true)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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"); | |
| } | |
| bool? hasNotification = info?.Notifications?.Contains("payment_received"); | |
| if (hasNotification is not true) | |
| { | |
| 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"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Plugins/BTCPayServer.Plugins.NIP05/NostrWalletConnectLightningClient.cs`
around lines 290 - 295, Update the condition in the notification capability
check around hasNotification so the get_info fallback executes whenever
payment_received has not been confirmed, including when the value is null.
Replace the false-only check with an is-not-true check, preserving the existing
request and response handling.

Users connecting rizful.com wallets via Nostr Wallet Connect reported that payments arrive but BTCPay never shows "payment received". The cause: rizful's wallet service only supports the current NIP-44 encryption, while the plugin still talks the deprecated NIP-04 — so its payment notifications are never received.
With this change the plugin reads what encryption the wallet service advertises and uses nip44 whenever available. A connection sticks to a single scheme — when nip44 is supported it is used exclusively, never mixed with nip04, since sending both would defeat the security purpose of nip44. Older wallets that don't advertise nip44 keep working over nip04 exactly as before, and existing connections need no settings change.
The server log now shows which encryption was negotiated and whether paid invoices are detected via push notifications or polling, which helps troubleshooting connection issues like this one.
Tested end-to-end on a mainnet BTCPay 2.4.2 instance with both rizful.com (nip44-only — unusable before this change) and Alby: connection validation, invoice creation, and payment settlement detected via nip44 push notifications (kind 23197). Works great on both.
Draft: depends on NNostr nip44 support () and needs an
NNostr.Clientrelease containing it; thePackageReferencehere still points at 0.0.54, so CI will fail to compile until that version bump. I'll update the reference and mark this ready once the package is out.Summary by CodeRabbit
New Features
Bug Fixes