From aca4529d728a6c692a4d4e3cb722a60090064d64 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 20:49:31 +0000 Subject: [PATCH] fix(nwc): drop authors and #p from response subscription filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some wallet services and relays don't produce or index the `p` tag on NIP-47 response events the way the spec implies for ephemeral kinds, which made Amethyst's strict relay-side filter (kinds + authors + #e + #p) match nothing while looser clients (Primal uses just kinds + #e) work against the same connection string. Reduce the relay filter to the same shape Primal uses. The request event id in #e is a unique 32-byte identifier, so the false-positive rate is effectively zero, and the wallet's identity is still authenticated end-to-end by NIP-04 decryption against the per-connection shared secret — the relay filter was never the security boundary. NWCPaymentQueryState no longer needs `fromServiceHex` or `toUserHex`; remove them and propagate the simpler ctor through callers. --- .../nip47WalletConnect/NwcSignerState.kt | 4 ---- .../nwc/FilterNWCPaymentsFromRequests.kt | 21 +++++++++---------- .../NWCFinderFilterAssemblerSubscription.kt | 4 +--- .../nwc/NWCPaymentFilterAssembler.kt | 7 ++++--- .../nwc/NWCPaymentWatcherSubAssembler.kt | 6 ++---- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip47WalletConnect/NwcSignerState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip47WalletConnect/NwcSignerState.kt index 1877c30d04..16e12e5914 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip47WalletConnect/NwcSignerState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip47WalletConnect/NwcSignerState.kt @@ -144,8 +144,6 @@ class NwcSignerState( val filter = NWCPaymentQueryState( - fromServiceHex = walletService.pubKeyHex, - toUserHex = event.pubKey, replyingToHex = event.id, relay = walletService.relayUri, ) @@ -185,8 +183,6 @@ class NwcSignerState( val filter = NWCPaymentQueryState( - fromServiceHex = walletService.pubKeyHex, - toUserHex = event.pubKey, replyingToHex = event.id, relay = walletService.relayUri, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/FilterNWCPaymentsFromRequests.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/FilterNWCPaymentsFromRequests.kt index 0bea1b6b89..920a3830c3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/FilterNWCPaymentsFromRequests.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/FilterNWCPaymentsFromRequests.kt @@ -24,17 +24,16 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentResponseEvent -fun filterNWCPaymentsFromRequests( - serviceKeys: Set, - paymentRequests: Set, - fromUsers: Set, -): Filter = +// The request event id (#e) is a unique 32-byte identifier — sufficient on +// its own to match the response. Adding `authors` or `#p` makes the filter +// strictly stricter at the relay, which causes silent timeouts on services +// or relays that don't set / index those fields the way NIP-47 expects +// (e.g. wallets that omit the `p` tag, relays that don't index single-letter +// tags on ephemeral kinds). Primal's client uses the same minimal filter +// (kinds + #e), and the wallet's identity is still authenticated end-to-end +// by NIP-04 decryption against the per-connection shared secret. +fun filterNWCPaymentsFromRequests(paymentRequests: Set): Filter = Filter( kinds = listOf(LnZapPaymentResponseEvent.KIND), - authors = serviceKeys.sorted(), - tags = - mapOf( - "e" to paymentRequests.sorted(), - "p" to fromUsers.sorted(), - ), + tags = mapOf("e" to paymentRequests.sorted()), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt index 00ca42f5ca..ff57b9212c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt @@ -49,11 +49,9 @@ fun NWCFinderFilterAssemblerSubscription( remember(note) { val zapPaymentRequestNote = note (zapPaymentRequestNote.event as? LnZapPaymentRequestEvent)?.let { noteEvent -> - noteEvent.walletServicePubKey()?.let { serviceId -> + noteEvent.walletServicePubKey()?.let { zapPaymentRequestNote.relays.map { NWCPaymentQueryState( - fromServiceHex = serviceId, - toUserHex = noteEvent.pubKey, replyingToHex = noteEvent.id, relay = it, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt index 46df66e2d3..e8f7c19534 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt @@ -26,11 +26,12 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -// This allows multiple screen to be listening to tags, even the same tag +// This allows multiple screen to be listening to tags, even the same tag. +// The subscription filter only needs the request event id (#e) — the wallet +// service's identity is authenticated by NIP-04 decryption against the +// per-connection shared secret, not by relay-side `authors`/`#p` filtering. @Stable class NWCPaymentQueryState( - val fromServiceHex: HexKey, - val toUserHex: HexKey, val replyingToHex: HexKey, val relay: NormalizedRelayUrl, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt index 4be5b34c46..7ba7e6c534 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt @@ -32,15 +32,13 @@ class NWCPaymentWatcherSubAssembler( if (keys.isEmpty()) return null return keys.groupBy { it.relay }.map { relayGroup -> - val fromAuthors = relayGroup.value.mapTo(mutableSetOf()) { it.fromServiceHex } val replyingToPayments = relayGroup.value.mapTo(mutableSetOf()) { it.replyingToHex } - val aboutUsers = relayGroup.value.mapTo(mutableSetOf()) { it.toUserHex } - if (fromAuthors.isEmpty() || replyingToPayments.isEmpty()) return null + if (replyingToPayments.isEmpty()) return null RelayBasedFilter( relay = relayGroup.key, - filter = filterNWCPaymentsFromRequests(fromAuthors, replyingToPayments, aboutUsers), + filter = filterNWCPaymentsFromRequests(replyingToPayments), ) } }