plugin: nwc: regularly re-broadcast info event
What changed, and why it matters
This change makes an Electrum NWC (Nostr Wallet Connect) server re-announce its capabilities to Nostr relays once per day. Previously it only announced once at startup, and some relays were dropping these announcements after a few days, causing wallets like Alby to stop working until the server was restarted. There is no security vulnerability here; it is a reliability fix for a wallet-connect plugin.
No security action required. Treat as a normal reliability improvement. Reviewers may optionally consider whether daily re-broadcasting could increase public metadata footprint on relays, but this is expected NIP-47 behavior and not a vulnerability.
Security signals we found
No security defect introduced
No change to cryptographic handling
No change to authorization or access control
No change to secret key usage pattern
No new dependencies or network endpoints
Reliability fix for relay-side event expiration behavior
Evidence from the diff
The commit converts publish_info_event() into publish_info_event_loop(), which loops forever and re-publishes the NIP-47 info event (kind 13194) for every active client connection every INFO_EVENT_REBROADCAST_INTERVAL_SEC (24 hours), with a 3-second stagger between clients. The info event contains supported methods, optional notifications, and supported encryption schemes. The change adds a membership check after sleeping to handle connections that may have been removed during the interval.
Changed components
electrum/plugins/nwc/nwcserver.pyNWCServer.publish_info_event_loop()Inspect captured patch +22 / −15
diff --git a/electrum/plugins/nwc/nwcserver.py b/electrum/plugins/nwc/nwcserver.py
index 7ea1bb0..9c1f5e7 100644
--- a/electrum/plugins/nwc/nwcserver.py
+++ b/electrum/plugins/nwc/nwcserver.py
@@ -184,6 +184,7 @@ class NWCServer(Logger, EventListener):
'list_transactions', 'notifications'}.union(SUPPORTED_SPENDING_METHODS)
SUPPORTED_NOTIFICATIONS: list[str] = ["payment_sent", "payment_received"]
SUPPORTED_ENCRYPTION_SCHEMES: set[str] = {'nip04'}
+ INFO_EVENT_REBROADCAST_INTERVAL_SEC = 60 * 60 * 24
def __init__(
self,
@@ -238,7 +239,7 @@ class NWCServer(Logger, EventListener):
try:
async with OldTaskGroup() as tg:
self.taskgroup = tg
- await tg.spawn(self.publish_info_event())
+ await tg.spawn(self.publish_info_event_loop())
await tg.spawn(self.handle_requests())
except asyncio.CancelledError:
if self.do_stop:
@@ -927,10 +928,11 @@ class NWCServer(Logger, EventListener):
new_budget_item = [new_msat, int(time.time())]
budget_spends.append(new_budget_item)
- async def publish_info_event(self):
+ async def publish_info_event_loop(self):
"""
Publishes the info event according to spec, announcing the supported methods.
We publish one info event for each client connection.
+ We re-publish info events regularly as some relays drop them after a couple days.
https://github.com/nostr-protocol/nips/blob/75f246ed987c23c99d77bfa6aeeb1afb669e23f7/47.md#example-nip-47-info-event
"""
tags = []
@@ -938,19 +940,24 @@ class NWCServer(Logger, EventListener):
tags.append(['notifications', ' '.join(self.SUPPORTED_NOTIFICATIONS)])
if self.SUPPORTED_ENCRYPTION_SCHEMES:
tags.append(['encryption', ' '.join(self.SUPPORTED_ENCRYPTION_SCHEMES)])
- for client_pubkey, connection in list(self.connections.items()):
- supported_methods = self.SUPPORTED_METHODS.copy()
- if self.is_receive_only(client_pubkey):
- supported_methods -= self.SUPPORTED_SPENDING_METHODS
- content = ' '.join(supported_methods)
- event_id = await aionostr._add_event(
- self.manager,
- kind=self.INFO_EVENT_KIND,
- tags=tags or None,
- content=content,
- private_key=connection['our_secret']
- )
- self.logger.debug(f"Published info event {event_id} to {client_pubkey}")
+ while True:
+ for client_pubkey, connection in list(self.connections.items()):
+ if client_pubkey not in self.connections:
+ continue # might was removed during sleep
+ supported_methods = self.SUPPORTED_METHODS.copy()
+ if self.is_receive_only(client_pubkey):
+ supported_methods -= self.SUPPORTED_SPENDING_METHODS
+ content = ' '.join(supported_methods)
+ event_id = await aionostr._add_event(
+ self.manager,
+ kind=self.INFO_EVENT_KIND,
+ tags=tags or None,
+ content=content,
+ private_key=connection['our_secret']
+ )
+ self.logger.debug(f"Published info event {event_id} to {client_pubkey}")
+ await asyncio.sleep(3) # try not to blast every event at once so they don't get rate limited
+ await asyncio.sleep(self.INFO_EVENT_REBROADCAST_INTERVAL_SEC)
def publish_notification_event(self, content: dict):
"""
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.