Requête Dual-Tag pour Événement Adressable Nostr
Problème
Lors de l'interrogation d'événements associés (commentaires, réactions, reposts, zaps) pour des événements adressables Nostr (Kind 30000-39999), la requête retourne moins de résultats ou aucun résultat même si un service d'indexation affiche le compte correct. Cela se produit car les événements adressables peuvent être référencés de deux façons, et différents clients utilisent différentes méthodes.
Contexte / Conditions de déclenchement
Utilisez cette skill quand :
- Une vidéo/post affiche un compte de 5 commentaires, mais la modal de commentaires dit « Aucun commentaire »
- Une API REST (comme Funnelcake) retourne les bons comptes d'engagement, mais les requêtes WebSocket retournent du vide
- Vous travaillez avec des commentaires NIP-22 (Kind 1111) sur des événements adressables
- Vous travaillez avec des réactions NIP-25 (Kind 7) sur des événements adressables
- Vous travaillez avec des reposts NIP-18 (Kind 6/16) sur des événements adressables
- Une feature quelconque interrogeant des événements qui référencent des événements Kind 30000-39999
Cause racine
Pour les événements adressables (Kind 30000-39999, comme les vidéos Kind 34236), les événements associés peuvent référencer la cible en utilisant soit :
-
E tag : Référence par ID d'événement (hex 64 caractères)
["E", "abc123...def456", "", "author-pubkey"] -
A tag : Référence par identifiant adressable (
kind:pubkey:d-tag)["A", "34236:abc123...def456:my-video-id", "", "author-pubkey"]
Différents clients et indexeurs Nostr utilisent différentes conventions :
- Certains utilisent toujours les E tags (basés sur l'ID d'événement)
- D'autres préfèrent les A tags pour les événements adressables (en suivant strictement NIP-22)
- Certains utilisent les deux tags
Si votre app n'interroge que par un type de tag, vous manquerez les événements taggés avec l'autre.
Solution
1. Mettre à jour la classe Filter (si nécessaire)
Assurez-vous que votre classe Filter supporte les requêtes avec A tag en majuscule :
// Dans la classe Filter
List<String>? uppercaseA; // Ajouter ce champ
// Dans toJson()
if (uppercaseA != null) {
data['#A'] = uppercaseA;
}
2. Interroger par BOTH Tags
Lors du chargement d'événements associés, exécutez deux requêtes parallèles et fusionnez les résultats :
Future<List<Event>> loadRelatedEvents({
required String eventId,
required String? addressableId, // Format: "kind:pubkey:d-tag"
}) async {
// Interroger par E tag (event ID)
final filterByE = Filter(
kinds: [targetKind],
uppercaseE: [eventId],
);
// Si addressable ID disponible, interroger aussi par A tag
if (addressableId != null && addressableId.isNotEmpty) {
final filterByA = Filter(
kinds: [targetKind],
uppercaseA: [addressableId],
);
// Exécuter les deux requêtes en parallèle
final results = await Future.wait([
nostrClient.queryEvents([filterByE]),
nostrClient.queryEvents([filterByA]),
]);
// Fusionner et dédupliquer par event ID
final eventMap = <String, Event>{};
for (final event in results[0]) {
eventMap[event.id] = event;
}
for (final event in results[1]) {
eventMap[event.id] = event;
}
return eventMap.values.toList();
}
return nostrClient.queryEvents([filterByE]);
}
3. Poster avec BOTH Tags
Lors de la création d'événements associés, incluez les E et A tags pour une compatibilité maximale :
final tags = <List<String>>[
// Toujours inclure E tag
['E', rootEventId, '', authorPubkey],
// Inclure A tag pour les événements adressables
if (rootAddressableId != null && rootAddressableId.isNotEmpty)
['A', rootAddressableId, '', authorPubkey],
// ... autres tags
];
4. Construire l'ID Adressable
Construisez l'identifiant adressable à partir des métadonnées d'événement :
String? get addressableId {
if (dTag == null) return null;
return '$kind:$pubkey:$dTag'; // ex: "34236:abc123...:my-video-id"
}
Vérification
Après implémentation :
- Trouvez un événement où l'API REST affiche count > 0 mais votre app affichait vide
- Vérifiez que la liste affiche maintenant les éléments attendus
- Postez un nouvel événement associé (commentaire/réaction)
- Vérifiez qu'il apparaît lors de l'interrogation par E ou A tag
Exemple
Avant correction - interroge seulement par E tag :
final filter = Filter(
kinds: [1111], // Commentaires
uppercaseE: [videoEventId], // Seulement E tag !
);
// Retourne 0 commentaires même si 5 existent (taggés avec A)
Après correction - interroge les deux tags :
final filterByE = Filter(kinds: [1111], uppercaseE: [videoEventId]);
final filterByA = Filter(kinds: [1111], uppercaseA: [addressableId]);
final results = await Future.wait([
client.queryEvents([filterByE]),
client.queryEvents([filterByA]),
]);
// Retourne les 5 commentaires peu importe comment ils ont été taggés
Notes
- Ce pattern s'applique à TOUTE feature qui interroge des événements référençant des événements adressables
- Le d-tag peut contenir des deux-points, donc analysez les ID adressables avec soin :
parts.sublist(2).join(':') - Pour les requêtes de compte (NIP-45), prenez le maximum des deux comptes (peut sur-compter si les événements ont les deux tags)
- Certains relays peuvent ne pas supporter les requêtes de filtre
#A- testez avec vos relays cibles - C'est particulièrement important pour l'interopérabilité entre différents clients Nostr
NIPs affectés
- NIP-22 : Commentaires (Kind 1111) - utilise E/A tags pour scope root
- NIP-25 : Réactions (Kind 7) - référence l'événement cible
- NIP-18 : Reposts (Kind 6/16) - référence l'événement reposté
- NIP-33 : Parameterized Replaceable Events (Kind 30000-39999) - définit le format adressable
- NIP-71 : Événements vidéo (Kind 34235/34236) - cas d'usage courant pour ce pattern
Références
- NIP-22: Comment - Spec de threading avec E/A tags
- NIP-33: Parameterized Replaceable Events - Format d'événement adressable
- Nostr Protocol Spec - Documentation générale Nostr