Follows
A user's follows live in their contact list, a kind 3 replaceable event with one p tag per followed pubkey. Follow sets are separate named lists (kind 30000) for grouping people, like "devs" or "friends".
Reading follows
user.contacts$ returns the followed users as User casts, so you can chain straight into each contact's profile or relays.
function ContactList({ user }: { user: User }) {
const contacts = use$(user.contacts$);
return contacts?.map((contact) => <ContactRow key={contact.pubkey} user={contact} />);
}
function ContactRow({ user }: { user: User }) {
const name = use$(user.profile$.displayName);
return <div>{name ?? user.npub}</div>;
}contacts$ waits for the user's outboxes and loads the contact list from them. It emits an empty array when the user has no contact list.
Example: Contact Manager
Subscribing without casts
eventStore.contacts() returns ProfilePointer[] instead of users. It is a shortcut for ContactsModel from applesauce-core/models.
eventStore.contacts(pubkey).subscribe((pointers) => console.log(pointers.length));PublicContactsModel and HiddenContactsModel return only the public or only the encrypted contacts.
Following and unfollowing
FollowUser and UnfollowUser add or remove a p tag on the current user's contact list and publish it to their outboxes. Both accept a pubkey or a ProfilePointer, and FollowUser creates the list if none exists.
await actions.run(FollowUser, pubkey);
await actions.run(UnfollowUser, { pubkey, relays: ["wss://relay.example.com"] });NewContacts creates a contact list from an array of pubkeys and throws if one already exists. See ActionRunner for setup.
Building contact lists
ContactsFactory from applesauce-common/factories builds kind 3 events. addContact() takes an optional petname, and removeContact() drops a pubkey.
const signed = await ContactsFactory.modify(event).addContact(friend, "alice").removeContact(spammer).sign(signer);Follow sets
Follow sets are addressable lists identified by their d tag. They can hold public and hidden (encrypted) users.
await actions.run(CreateFollowSet, "Devs", { description: "People building on nostr", public: [alice, bob] });
await actions.run(AddUserToFollowSet, carol, "devs-identifier", true); // hidden
await actions.run(RemoveUserFromFollowSet, bob, "devs-identifier");
await actions.run(UpdateFollowSetInformation, "devs-identifier", { title: "Builders" });The set actions take either the d identifier or the set event itself. Use FollowSetFactory to build sets directly, and getProfilePointersFromList(event) from applesauce-common/helpers to read the users in one.
Helpers
Helpers in applesauce-core/helpers work on raw contact list events:
getContacts(event)returns public and unlocked hidden contacts mergedgetPublicContacts(event)andgetHiddenContacts(event)return one group eachunlockHiddenContacts(event, signer)decrypts hidden contactsgetRelaysFromContactsEvent(event)reads the legacy relay map some clients store in the content
Best Practices
- Use
FollowUserandUnfollowUserinstead of rebuilding the list, so follows added by other clients aren't lost - Make sure the contact list has loaded before following, since a missing list is treated as empty
- Render contacts from
user.contacts$so each row can subscribe to its own profile - Use follow sets for grouping people instead of extra contact lists