From ef7f5aa8d713bf0e754607f9fcc0527c5a10e737 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 1 Aug 2026 12:45:01 -0700 Subject: Surface refused relay publishes; publish NWC info once per session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit relay.coinos.io's write policy was rejecting every 23195/13194 hal sent (kinds were whitelisted to coinos server pubkeys — incompatible with hal's per-connection service keys) and the rejections temp-banned the IP. publishOn swallowed all of it, which disguised a relay-side policy problem as a client bug. The relay policy is fixed separately; here, refused publishes now log the relay and reason, and the 13194 info event goes out once per connection per session instead of on every listen() so replies keep their share of the relay's per-IP rate budget. Co-Authored-By: Claude Fable 5 --- src/features/nwc.js | 15 +++++++++++---- src/nostr.js | 13 ++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/features/nwc.js b/src/features/nwc.js index 66d348f..f561a8a 100644 --- a/src/features/nwc.js +++ b/src/features/nwc.js @@ -339,6 +339,7 @@ export function nwcFeature(ctx) { // ---- lifecycle -------------------------------------------------------- let unsubs = []; + const infoPublished = new Set(); // connection ids whose 13194 went out this session let listenGen = 0; // TEMP instrumentation: which listen() generation is live function stop() { if (unsubs.length) console.log(`nwc: stop() closing ${unsubs.length} sub(s) from gen ${listenGen}`); @@ -364,10 +365,16 @@ export function nwcFeature(ctx) { }, { onclose: (reasons) => console.log(`nwc: gen ${gen} sub CLOSED for conn=${c.id}:`, JSON.stringify(reasons)) }, )); - // Republish the capability event on every start: a client that can't - // find kind 13194 just spins, and a single publish at creation time can - // easily have been missed by a relay. - publishInfo(c).catch(() => {}); + // Republish the capability event once per session per connection: a + // client that can't find kind 13194 just spins, and a single publish at + // creation time can easily have been missed by a relay. But listen() + // re-runs on every sync snapshot and wake, and relays rate-limit + // accepted events per IP — repeated republishing eats the budget the + // replies need (and once got this IP temp-banned). + if (!infoPublished.has(c.id)) { + infoPublished.add(c.id); + publishInfo(c).catch(() => {}); + } } } diff --git a/src/nostr.js b/src/nostr.js index e1f1c14..006a74c 100644 --- a/src/nostr.js +++ b/src/nostr.js @@ -37,7 +37,18 @@ export function subscribeOn(relays, filter, onEvent, extra = {}) { } catch { return () => {}; } } export async function publishOn(relays, evt) { - try { await Promise.allSettled(pool.publish(relays, evt)); return true; } catch { return false; } + try { + const results = await Promise.allSettled(pool.publish(relays, evt)); + // A rejected publish is a relay refusing the event (policy, rate limit, + // ban). Swallowing that silently once hid a relay-side rejection behind + // what looked like a client bug — always say which relay refused and why. + results.forEach((r, i) => { + if (r.status === 'rejected') { + console.warn(`nostr: publish kind ${evt.kind} refused by ${relays[i]}:`, r.reason?.message || r.reason); + } + }); + return results.some((r) => r.status === 'fulfilled'); + } catch { return false; } } // --- locking a gift to a recipient's Nostr key --------------------------- -- cgit v1.2.3