You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a "Followed by" / Followers view that surfaces who follows the user on Nostr. Today nostr-mail only shows the outbound direction — the user's own follow list — and has no way to see who has added the user as a contact.
Current state in the codebase
The backend (tauri-app/backend/src/nostr.rs) only resolves the following direction by reading the user's own kind 3 (ContactList) event:
fetch_following_profiles(...) — fetches the user's kind 3 event and resolves profiles for the p-tagged pubkeys they follow.
fetch_following_pubkeys(...) — returns the bech32 npubs the user follows.
There is no function that resolves the reverse (followers). The Contacts page is populated from the follow list only.
Why this is non-trivial on Nostr (research)
Nostr follow lists (NIP-02, kind 3) are one-directional: an event lists who you follow, with no inverse index. To compute followers, a client must find every kind 3 event anywhere that contains the user's pubkey in a p tag — which a single relay generally cannot answer fully, and reverse-querying/counting all kind-3 events referring to a pubkey is prohibitively expensive to do naively. (NIP-02, Kind 3: Follow List)
This is fundamentally a social-graph indexing problem, which is exactly what a graph service is built to answer.
How other apps have handled it
Indexer/aggregator services — clients historically relied on indexing services (notably nostr.band) that crawled relays and maintained a reverse follow index, exposing follower lists/counts via API. ⚠️ Update: nostr.band is no longer available, which removes the de-facto third-party follower index much of the ecosystem depended on, and is a strong argument for using our own graph infrastructure rather than a third party.
NIP-45 COUNT — relays that support the COUNT verb can return an aggregate follower count ({"count": N}) without streaming every event, though support is inconsistent and the NIP has been contested. (NIP-45)
Best-effort relay query — simpler clients subscribe for kind 3 events with a #p filter for the user's pubkey across configured relays and show whatever they find, accepting that it's incomplete.
Caveat widely noted: for popular accounts a large share of "followers" are bots/spam, so raw counts are noisy.
Preferred approach: use our own Graph Name Service (asherp/GNS)
Rather than depend on a third-party indexer (now that nostr.band is gone), this is a good driver to lean on our own GNS (Graph Name System) project. GNS is a decentralized naming/discovery layer built on the Nostr social graph: it already runs Nostr relay clients with relay attribution, does BFS graph traversal over follow lists, and exposes an HTTP API with a TTL-based cache. Computing followers is the reverse-edge case of the graph it already builds.
nostr-mail backend (nostr.rs): add a fetch_followers(...) (+ optional fetch_followers_count(...)) that primarily queries GNS via its HTTP API, with a fallback to a best-effort relay #p kind-3 query (and optionally NIP-45 COUNT) when GNS is unavailable. Resolve profiles for the resulting pubkeys (reuse the path from fetch_following_profiles).
Frontend: surface results — e.g. a "Followers" / "Followed by" tab or sub-tab on the Contacts page, and/or a follower count on the Profile page. Indicate mutual relationships ("follows you" / "you follow") by cross-referencing the existing following list. GNS additionally enables provenance / graph-relative naming (e.g. how you're connected to a follower), which is a richer UX than a flat count.
Caching: cache follower results in the local SQLite store (like contacts are cached) since the query is expensive and incomplete in real time.
Acceptance criteria
Backend can resolve followers (pubkeys + profiles) for the current user, primarily via GNS, with a graceful relay-query fallback and handling of incompleteness.
UI surfaces followers (list and/or count), e.g. on Contacts and/or Profile.
Mutual-follow state is indicated where known (cross-referenced with the existing following list).
Results are cached locally to avoid repeated expensive queries.
Clear UX messaging that follower data is best-effort/approximate on Nostr.
Open questions
Data source priority: GNS as primary (preferred) with relay #p fallback — confirm. Do we still want NIP-45 COUNT as a fast-count option where supported?
GNS deployment: which GNS endpoint does nostr-mail talk to (self-hosted vs. a hosted instance), and is it configurable in Settings?
Where to surface it: a Contacts sub-tab, the Profile page, or both? Show provenance/paths from GNS, or just a flat follower list to start?
Spam/bot filtering — apply any trust-rank/heuristics, or show raw results?
Privacy: querying an external GNS instance still reveals interest in a pubkey — default-on or opt-in?
Summary
Add a "Followed by" / Followers view that surfaces who follows the user on Nostr. Today nostr-mail only shows the outbound direction — the user's own follow list — and has no way to see who has added the user as a contact.
Current state in the codebase
The backend (
tauri-app/backend/src/nostr.rs) only resolves the following direction by reading the user's own kind 3 (ContactList) event:fetch_following_profiles(...)— fetches the user's kind 3 event and resolves profiles for thep-tagged pubkeys they follow.fetch_following_pubkeys(...)— returns the bech32 npubs the user follows.There is no function that resolves the reverse (followers). The Contacts page is populated from the follow list only.
Why this is non-trivial on Nostr (research)
Nostr follow lists (NIP-02, kind 3) are one-directional: an event lists who you follow, with no inverse index. To compute followers, a client must find every kind 3 event anywhere that contains the user's pubkey in a
ptag — which a single relay generally cannot answer fully, and reverse-querying/counting all kind-3 events referring to a pubkey is prohibitively expensive to do naively. (NIP-02, Kind 3: Follow List)This is fundamentally a social-graph indexing problem, which is exactly what a graph service is built to answer.
How other apps have handled it
COUNT— relays that support theCOUNTverb can return an aggregate follower count ({"count": N}) without streaming every event, though support is inconsistent and the NIP has been contested. (NIP-45)#pfilter for the user's pubkey across configured relays and show whatever they find, accepting that it's incomplete.Preferred approach: use our own Graph Name Service (asherp/GNS)
Rather than depend on a third-party indexer (now that nostr.band is gone), this is a good driver to lean on our own GNS (Graph Name System) project. GNS is a decentralized naming/discovery layer built on the Nostr social graph: it already runs Nostr relay clients with relay attribution, does BFS graph traversal over follow lists, and exposes an HTTP API with a TTL-based cache. Computing followers is the reverse-edge case of the graph it already builds.
Proposed direction:
nostr.rs): add afetch_followers(...)(+ optionalfetch_followers_count(...)) that primarily queries GNS via its HTTP API, with a fallback to a best-effort relay#pkind-3 query (and optionally NIP-45COUNT) when GNS is unavailable. Resolve profiles for the resulting pubkeys (reuse the path fromfetch_following_profiles).Acceptance criteria
Open questions
#pfallback — confirm. Do we still want NIP-45COUNTas a fast-count option where supported?Related
Sources
COUNT)