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 16e12e5914..2647288d1a 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,6 +144,7 @@ class NwcSignerState( val filter = NWCPaymentQueryState( + toUserHex = event.pubKey, replyingToHex = event.id, relay = walletService.relayUri, ) @@ -183,6 +184,7 @@ class NwcSignerState( val filter = NWCPaymentQueryState( + 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 920a3830c3..245ac84444 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 @@ -25,15 +25,28 @@ import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentResponseEvent // 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 = +// its own to match a spec-compliant kind-23195 response. We deliberately keep +// `authors` out of the filter because some wallet services route through a +// signer pubkey that differs from the one advertised in the connection URI, +// which made the previous `authors`-strict filter time out. +// +// We DO keep `#p: [client pubkey]` because purpose-built NWC relays +// (e.g. relay.getalby.com/v1) use the `#p` tag as the routing key — without +// it the relay never delivers the response to our subscription, even though +// the event is well-formed. Spec-compliant responses always set `p` to the +// client pubkey, so including `#p` does not exclude any conforming wallet. +// The wallet's identity is still authenticated end-to-end by NIP-04 +// decryption against the per-connection shared secret AND by a client-side +// `event.pubKey == request.p` check in NwcPaymentTracker. +fun filterNWCPaymentsFromRequests( + paymentRequests: Set, + fromUsers: Set, +): Filter = Filter( kinds = listOf(LnZapPaymentResponseEvent.KIND), - tags = mapOf("e" to paymentRequests.sorted()), + tags = + mapOf( + "e" to paymentRequests.sorted(), + "p" to fromUsers.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 ff57b9212c..2300b7ce3d 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 @@ -52,6 +52,7 @@ fun NWCFinderFilterAssemblerSubscription( noteEvent.walletServicePubKey()?.let { zapPaymentRequestNote.relays.map { NWCPaymentQueryState( + 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 e8f7c19534..ef171dafde 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 @@ -27,11 +27,14 @@ 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. -// 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. +// The subscription filter carries `#e: [request id]` plus `#p: [client pubkey]`. +// The `#p` field is the routing key for purpose-built NWC relays (e.g. +// relay.getalby.com/v1) — dropping it caused those relays to silently never +// deliver the response. `authors` is intentionally left out because some +// wallets sign responses with a key different from the URI-advertised one. @Stable class NWCPaymentQueryState( + 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 7ba7e6c534..b179e5a042 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 @@ -33,12 +33,13 @@ class NWCPaymentWatcherSubAssembler( return keys.groupBy { it.relay }.map { relayGroup -> val replyingToPayments = relayGroup.value.mapTo(mutableSetOf()) { it.replyingToHex } + val aboutUsers = relayGroup.value.mapTo(mutableSetOf()) { it.toUserHex } if (replyingToPayments.isEmpty()) return null RelayBasedFilter( relay = relayGroup.key, - filter = filterNWCPaymentsFromRequests(replyingToPayments), + filter = filterNWCPaymentsFromRequests(replyingToPayments, aboutUsers), ) } }