NIP-02 ====== Follow List ----------- `final` `optional` A special event with kind `3`, meaning "follow list" is defined as having a list of `p` tags, one for each of the followed/known profiles one is following. Each tag entry should contain the key for the profile, a relay URL where events from that key can be found (can be set to an empty string if not needed), and a local name (or "petname") for that profile (can also be set to an empty string or not provided), i.e., `["p", <32-bytes hex key>,
, ]`. The `.content` is not used. For example: ```jsonc { "kind": 3, "tags": [ ["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"], ["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"], ["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"] ], "content": "", // other fields... } ``` Every new following list that gets published overwrites the past ones, so it should contain all entries. Relays and clients SHOULD delete past following lists as soon as they receive a new one. Whenever new follows are added to an existing list, clients SHOULD append them to the end of the list, so they are stored in chronological order. ## Uses ### Follow list backup If one believes a relay will store their events for sufficient time, they can use this kind-3 event to backup their following list and recover on a different device. ### Profile discovery and context augmentation A client may rely on the kind-3 event to display a list of followed people by profiles one is browsing; make lists of suggestions on who to follow based on the follow lists of other people one might be following or browsing; or show the data in other contexts. ### Relay sharing A client may publish a follow list with good relays for each of their follows so other clients may use these to update their internal relay lists if needed, increasing censorship-resistance. ### Petname scheme The data from these follow lists can be used by clients to construct local ["petname"](http://www.skyhunter.com/marcs/petnames/IntroPetNames.html) tables derived from other people's follow lists. This alleviates the need for global human-readable names. For example: A user has an internal follow list that says ```json [ ["p", "21df6d143fb96c2ec9d63726bf9edc71", "", "erin"] ] ``` And receives two follow lists, one from `21df6d143fb96c2ec9d63726bf9edc71` that says ```json [ ["p", "a8bb3d884d5d90b413d9891fe4c4e46d", "", "david"] ] ``` and another from `a8bb3d884d5d90b413d9891fe4c4e46d` that says ```json [ ["p", "f57f54057d2a7af0efecc8b0b66f5708", "", "frank"] ] ``` When the user sees `21df6d143fb96c2ec9d63726bf9edc71` the client can show _erin_ instead; When the user sees `a8bb3d884d5d90b413d9891fe4c4e46d` the client can show _david.erin_ instead; When the user sees `f57f54057d2a7af0efecc8b0b66f5708` the client can show _frank.david.erin_ instead. ## GitRepublic Usage GitRepublic uses KIND 3 (Contact List) events to enable repository filtering based on social connections. ### Repository Filtering When a user enables the "Show only my repos and those of my contacts" filter on the landing page, GitRepublic: 1. **Fetches the user's contact list**: Retrieves the latest KIND 3 event published by the logged-in user 2. **Extracts contact pubkeys**: Parses all `p` tags from the contact list event to build a set of followed pubkeys 3. **Includes the user's own pubkey**: Automatically adds the logged-in user's pubkey to the filter set 4. **Filters repositories**: Shows only repositories where: - The repository owner (event `pubkey`) is in the contact set, OR - Any maintainer (from `maintainers` tags) is in the contact set ### Implementation Details - **Pubkey normalization**: GitRepublic handles both hex-encoded pubkeys and bech32-encoded npubs in contact lists - **Maintainer matching**: The filter checks all maintainers listed in repository announcement `maintainers` tags - **Real-time updates**: The contact list is fetched when the user logs in and can be refreshed by reloading the page ### Example Use Case A user follows several developers on Nostr. By enabling the contact filter: - They see repositories owned by people they follow - They see repositories where their contacts are maintainers - They see their own repositories - They don't see repositories from people they don't follow This creates a personalized, social discovery experience for finding relevant code repositories based on trust relationships established through Nostr's follow mechanism. ### Technical Implementation ```typescript // Fetch user's kind 3 contact list const contactEvents = await nostrClient.fetchEvents([ { kinds: [KIND.CONTACT_LIST], // KIND 3 authors: [userPubkey], limit: 1 } ]); // Extract pubkeys from 'p' tags const contactPubkeys = new Set(); contactPubkeys.add(userPubkey); // Include user's own repos if (contactEvents.length > 0) { const contactEvent = contactEvents[0]; for (const tag of contactEvent.tags) { if (tag[0] === 'p' && tag[1]) { let pubkey = tag[1]; // Decode npub if needed try { const decoded = nip19.decode(pubkey); if (decoded.type === 'npub') { pubkey = decoded.data as string; } } catch { // Assume it's already a hex pubkey } contactPubkeys.add(pubkey); } } } // Filter repositories const filteredRepos = allRepos.filter(event => { // Check if owner is in contacts if (contactPubkeys.has(event.pubkey)) return true; // Check if any maintainer is in contacts const maintainerTags = event.tags.filter(t => t[0] === 'maintainers'); for (const tag of maintainerTags) { for (let i = 1; i < tag.length; i++) { let maintainerPubkey = tag[i]; // Decode npub if needed try { const decoded = nip19.decode(maintainerPubkey); if (decoded.type === 'npub') { maintainerPubkey = decoded.data as string; } } catch { // Assume it's already a hex pubkey } if (contactPubkeys.has(maintainerPubkey)) return true; } } return false; }); ``` This implementation provides a seamless way for users to discover repositories from their social network while maintaining the decentralized, trustless nature of Nostr.