Lists

high level

Example

final myset = await ndk.lists.getSetByName(
  name: "myset",
  kind: Nip51List.kRelaySet,
  customSigner: mySigner,
);

if (myset == null) {
  print("set not found");
  return;
}
print("received a set with ${myset.elements.length} elements");

How to use

We distinguish between lists and sets:

  • Lists: Single lists identified by kind (e.g., bookmarks, mute list)
  • Sets: Named collections identified by kind + name/d-tag (e.g., relay sets, follow sets)

Both can have public and private (encrypted) elements.

Current behavior

For public list/set content:

  • reads use the latest visible event for the relevant list or set
  • cached results are reused on later reads

For private list/set content:

  • the original event content stays encrypted
  • decrypted private tags can be loaded through NDK's decrypted payload cache
  • repeated reads can reuse cached plaintext instead of decrypting again

Lists Methods

getSingleNip51List

Retrieves a NIP-51 list by kind.

  return _decryptedEventPayloads.loadOrDecrypt(
    event: event,
    viewerPubKey: signer.getPublicKey(),
    scheme:
        isNip04 ? DecryptedPayloadScheme.nip04 : DecryptedPayloadScheme.nip44,
    decrypt: () => isNip04
        // ignore: deprecated_member_use_from_same_package
        ? signer.decrypt(event.content, signer.getPublicKey())
        : signer.decryptNip44(
            ciphertext: event.content,
            senderPubKey: signer.getPublicKey(),
          ),
  );
}

Future<void> _applyPrivateTags({
  required Nip01Event event,

addElementToList

Adds an element to a list. Creates the list if it doesn't exist.

}

Future<Nip51Set?> _parseSetEvent(
  Nip01Event event,
  EventSigner? signer,
) async {
  final set = await Nip51Set.fromEvent(event, null);
  if (set != null && signer != null) {
    await _applyPrivateTags(event: event, signer: signer, list: set);
  }
  return set;
}

///* lists *///

Future<Nip51List?> _getCachedNip51List(int kind, EventSigner signer) async {
  List<Nip01Event>? events = await _cacheManager.loadEvents(
    pubKeys: [signer.getPublicKey()],
    kinds: [kind],
  );

removeElementFromList

Removes an element from a list.

    ).stream) {
      if (refreshedList == null ||
          refreshedList.createdAt <= event.createdAt) {
        refreshedList = await _parseListEvent(event, signer);
        // if (Helpers.isNotBlank(event.content)) {
        //   Nip51List? decryptedList = await Nip51List.fromEvent(event, signer);
        //   refreshedList = decryptedList;
        // }
        await _cacheManager.saveEvent(event);
      }
    }
    return refreshedList;
  }
  return list;
}

/// Returns a NIP-51 list by kind for a given public key.
///

Sets Methods

getSetByName

Gets a specific set by name (d-tag) and kind.

/// [tag] the tag type of the element to remove \
/// [value] the value to remove from the list \
/// [broadcastRelays] optional specific relays to broadcast to
///
/// Returns the updated list, or null if the list doesn't exist.\
/// Throws an exception if no event signer is available.
Future<Nip51List?> removeElementFromList({
  required int kind,
  required String tag,
  required String value,
  Iterable<String>? broadcastRelays,
}) async {
  if (_eventSigner == null) {
    throw Exception(
      "cannot broadcast private nip51 list without a signer that can sign",
    );
  }
  Nip51List? list = await getSingleNip51List(kind, forceRefresh: true);

getPublicSets

Returns a stream of all public sets for a given public key and kind.

    kinds: [kind],
  );
  events = events.where((event) {
    if (event.getDtag() != null && event.getDtag() == name) {
      return true;
    }
    return false;
  }).toList();
  events.sort((a, b) => b.createdAt.compareTo(a.createdAt));
  return events.isNotEmpty
      ? await _parseSetEvent(events.first, signer)
      : null;
}

/// get a nip51 set

addElementToSet

Adds an element to a named set. Creates the set if it doesn't exist.

}) {
  final relaySets = <String, Nip51Set>{};

  return _requests
      .query(
        filters: [
          Filter(authors: [signer.getPublicKey()], kinds: [kind]),
        ],
        cacheRead: !forceRefresh,
      )
      .stream
      .where((event) => event.getDtag() != null)
      .asyncMap((event) async {
        final dtag = event.getDtag()!;
        final existingSet = relaySets[dtag];

        if (existingSet == null || existingSet.createdAt < event.createdAt) {
          final newSet = await _parseSetEvent(event, signer);
          if (newSet != null) {
            await _cacheManager.saveEvent(event);
            relaySets[newSet.name] = newSet;

removeElementFromSet

Removes an element from a named set.

if (relaySet == null || forceRefresh) {
  Nip51Set? newRelaySet;
  await for (final event in _requests.query(
    filters: [
      Filter(
        authors: [signer.getPublicKey()],
        kinds: [kind],
        tags: {
          "#d": [name],
        },
      ),
    ],
    cacheRead: !forceRefresh,
  ).stream) {
    if (newRelaySet == null || newRelaySet.createdAt < event.createdAt) {
      if (event.getDtag() != null && event.getDtag() == name) {
        newRelaySet = await _parseSetEvent(event, signer);
        await _cacheManager.saveEvent(event);
      } else if (Helpers.isNotBlank(event.content)) {
        Nip51Set? decryptedRelaySet = await _parseSetEvent(event, signer);
        if (decryptedRelaySet != null && decryptedRelaySet.name == name) {
          newRelaySet = decryptedRelaySet;

setCompleteSet

Overwrites or creates a complete set. Warning: This replaces the entire set.

/// [name] name of the set (d tag identifier) \
/// [tag] the tag type for the element (e.g., 'relay', 'p', 'e') \
/// [value] the value to add to the set \
/// [kind] kind of the set \
/// [private] if true, encrypt the element in the set content \
/// [specificRelays] optional specific relays to broadcast to
///
/// Returns the updated set.
Future<Nip51Set?> addElementToSet({
  required String name,
  required String tag,
  required String value,
  required int kind,
  bool private = false,
  Iterable<String>? specificRelays,

deleteSet

Deletes a set by name and broadcasts a deletion event.

  events = events.where((event) {
    if (event.getDtag() != null && event.getDtag() == name) {
      return true;
    }
    return false;
  }).toList();
  for (final event in events) {
    _cacheManager.removeEvent(event.id);
  }

  await _cacheManager.saveEvent(event);
  return set;
}

/// Removes an element from a NIP-51 set.

Common Use Cases

Relay Sets

// Add relay to a set
await ndk.lists.addElementToSet(
  name: "my-relays",
  tag: "relay",
  value: "wss://relay.example.com",
  kind: Nip51List.kRelaySet,
);

// Get a relay set
final relaySet = await ndk.lists.getSetByName(
  name: "my-relays",
  kind: Nip51List.kRelaySet,
);

Bookmarks

// Add bookmark
await ndk.lists.addElementToList(
  kind: Nip51List.kBookmarks,
  tag: "e",
  value: eventId,
);

// Get bookmarks
final bookmarks = await ndk.lists.getSingleNip51List(
  Nip51List.kBookmarks,
  mySigner,
);

Follow Sets

// Add to follow set
await ndk.lists.addElementToSet(
  name: "close-friends",
  tag: "p",
  value: pubkey,
  kind: Nip51List.kFollowSet,
);

// Stream all public follow sets
ndk.lists.getPublicSets(
  kind: Nip51List.kFollowSet,
  publicKey: somePubkey,
).listen((sets) {
  print("Found ${sets?.length ?? 0} follow sets");
});