Nostr Compass #26
L’organisation Marmot Protocol ouvre trois nouveaux dépôts pour un projet de protocole v2 et une lignée de client native : un workspace Rust nommé darkmatter, une app iOS SwiftUI darkmatter-ios, et une app Android Kotlin/Compose darkmatter-android. Le Whitenoise Flutter original est archivé. Chama compresse dix-sept versions en une seule semaine et franchit la ligne d’app autonome à v3.0.0 avant d’atterrir une refonte complète de l’UI de salle de trade et des vitrines par vendeur dans v3.1.0, en plus des parts Shamir uniquement pour les détenteurs, la substitution d’arbitre, le routage de communauté mondiale, et les notifications de trade de bout en bout. Coracle lance un service de relais hébergé payant soutenu par la pile open source Caravel et zooid, avec une intégration Flotilla profonde planifiée. Angor bascule sur mainnet par défaut dans v0.2.30 et atterrit un test de financement UAT à 3 utilisateurs dans v0.2.29. Amethyst atterrit 41 PRs non publiées poursuivant le travail NIP-32 / NIP-F4 / Tor de la semaine dernière. NIP-67 (indice de complétude EOSE) et l’autocomplétion NIP-50 fusionnent, fermant deux lacunes de correction de longue date dans le protocole de relais principal. NIP-GART propose un format de câble préservant la vie privée pour les alertes d’urgence, et NIP-46 gagne une méthode de déconnexion.
Articles principaux
Marmot v2 (Dark Matter) : refonte du protocole, clients natifs, app Flutter archivée
Trois nouveaux dépôts ont fait surface sous l’organisation GitHub marmot-protocol cette semaine, formant ensemble la forme précoce d’un projet de protocole Marmot v2 et d’une lignée de client native qui remplace la ligne d’app Flutter. darkmatter (Rust, créé le 13 mai, trente-quatre commits dans les sept derniers jours) contient le projet de protocole v2 dans spec/, un moteur CGKA basé sur OpenMLS dans crates/cgka-engine, un simulateur de conformité avec des tests de propriété, et un modèle formel Tamarin pour les preuves de convergence. darkmatter-ios (Swift, créé le 25 mai) est un client SwiftUI soutenu par un xcframework UniFFI MarmotKit vendu généré à partir du workspace Rust. darkmatter-android (Kotlin/Jetpack Compose, créé le 25 mai) repose sur les mêmes bindings Rust. Le Whitenoise Flutter original a été marqué whitenoise-archive (« ARCHIVED: This was the original White Noise Flutter app ») ; un nouveau dépôt Dart whitenoise porte la ligne Flutter active en parallèle.
Lisez cela comme des progrès précoces vers un Marmot plus fiable, pas comme un pivot terminé. Le README de darkmatter s’étiquette « Projet candidat de protocole Marmot v2, moteur CGKA, et workspace de conformité » et dit directement : « MDK reste l’implémentation de protocole Rust déployée jusqu’à ce que ce projet et ce moteur soient adoptés. » À l’intérieur du workspace, le crate cgka-engine est étiqueté 0.1.0, « consommateur interne unique, non-stable-semver ». Chaque page de spécification porte « Statut : projet pour revue interne ». Trois étoiles sur le dépôt workspace et zéro sur les apps iOS et Android confirment que le travail est pré-annonce. Direction, portée et discipline sont le signal ici ; la préparation à la production n’est pas la revendication.
Le projet de protocole rend les deltas v1-à-v2 concrets. L’extension MLS marmot_group_data monolithique de MIP-01, qui a porté le nom, la description, les pubkeys d’admin, l’id de routage de groupe Nostr, la liste de relais, les données d’image de groupe, et les paramètres de messages éphémères sous un seul parapluie depuis le début de Marmot, est divisée en composants d’app versionnés : marmot.group.profile.v1 pour le nom et la description, marmot.group.admin-policy.v1 pour les pubkeys d’admin, marmot.transport.nostr.routing.v1 pour le nostr_group_id aléatoire et la liste de relais canonique, marmot.group.blossom.image.v1 pour le hash d’image, la clé de chiffrement, le nonce, et la clé d’upload, et marmot.group.message-retention.v1 pour les secondes de messages éphémères. Chaque composant possède ses octets exacts et son propre chemin de versionnement, afin qu’une future fonctionnalité puisse rev un composant sans forcer le reste de l’état de groupe à repasser sur le consensus d’extension MLS. Les identifiants MIP-00 gagnent également un nouveau document de fondation account-identity-proof-v1.md, appelé « nouveau dans v2 et cassant ». La preuve d’identité vit maintenant sur sa propre surface, séparée de la construction de KeyPackage.
Les deltas de bibliothèque soutiennent la refonte de la spécification. cgka-engine est la nouvelle machine d’état de groupe locale : elle enveloppe OpenMLS, possède les états d’époque Stable, PendingPublish, Merging, et Recovering, traduit les intents en commits MLS, retourne des valeurs IngestOutcome et GroupEvent typées pour chaque enveloppe de transport entrante, et ne livre explicitement aucun transport et aucune persistance. Un trait TransportPeeler sépare Nostr du moteur, et un trait StorageProvider sépare SQLite (via storage-sqlite, soutenu par SQLCipher) du moteur. Aujourd’hui, MDK emballe tout cela ensemble ; diviser les couches permet à un moteur de siéger sous un transport de relais Nostr maintenant et les transports flux QUIC et broker également livrés plus tard, sans réécriture du modèle de convergence. La convergence elle-même est documentée comme distributed-convergence.md et prouvée dans un modèle Tamarin qui couvre la sélection de branche déterministe, l’éligibilité gate par politique, la relecture d’ancre retenue, le rejet de branche périmée, la réorganisation de livraison, la duplication, l’invalidation de sortie d’app, le passage welcome/commit, la consommation de proposition, et le gating sortant pendant la synchronisation. Les tests de propriété Rust vérifient ensuite que le moteur suit les mêmes règles avec de vrais objets OpenMLS et le harnais de simulateur. Le travail de fiabilité par méthodes formelles de cette portée est absent de la pile Marmot actuelle.
Les deux clients natifs abandonnent Flutter pour des boîtes à outils UI natives de plateforme. darkmatter-ios est du SwiftUI pur avec une Notification Service Extension qui déchiffre les réveils push MIP-05 sur l’appareil, vend un paquet Swift MarmotKit généré construit à partir du workspace Rust, et s’enregistre sous l’ID de bundle dev.ipf.darkmatter et le groupe d’app. darkmatter-android est Kotlin et Jetpack Compose, avec un build piloté par just qui produit un APK arm64-v8a signé et lit les endpoints de télémétrie depuis local.properties. Le README Android énonce le principe architectural directement : « Dark Matter possède les données de protocole et les stocke dans SQLite. L’app Android devrait rendre ces données, gérer le comportement de plateforme Android, et garder l’état de cycle de vie UI. L’app Android ne devrait pas devenir une seconde base de données pour les données Dark Matter. » Cela reflète la discipline de frontière que le README cgka-engine applique dans la couche Rust, appliquée à la couche UI.
Les clients natifs comptent pour Marmot parce que la faiblesse la plus citée du protocole a été la fiabilité mobile sous des conditions de livraison inégales : réveils de notification à échéance manquée, courses de commits MLS pendant les fluctuations réseau, limites de récupération en arrière-plan qui bloquent les avancements d’époque. SwiftUI et Compose donnent aux clients un accès direct aux primitives de traitement en arrière-plan de plateforme que Flutter atteint via un pont de plugin, et le chemin de binding UniFFI garde la logique de protocole dans un workspace Rust unique livré comme bibliothèque statique sur les deux plateformes. La ligne Whitenoise Flutter continue dans le dépôt whitenoise non archivé, donc l’annonce est additive : une nouvelle lignée de client native fonctionne aux côtés de l’app Flutter pendant que la spécification v2 converge. Le passage de production depuis MDK ou le Whitenoise actuel attend que le projet, le moteur et les clients atteignent des sorties prêtes pour la production.
Chama v2.0.0 à v3.1.0 : entiercement P2P autonome en une semaine
Le client d’entiercement P2P Nostr-natif introduit dans Newsletter #25 à v1.3.0 a livré dix-sept versions étiquetées sur les sept derniers jours, se terminant à v3.1.0 le 9 juin avec une refonte de l’UI de salle de trade et des vitrines par vendeur. La piste de versions raconte l’histoire : v2.0.0 est la base BREAKING, puis v2.0.1, v2.0.2, et v2.0.3 ferment les lacunes de rail de financement Fedi WebView ; v2.1.0, v2.2.0, v2.3.0, et v2.3.1 durcissent la couche d’arbitre ; v2.4.0, v2.5.0, et v2.6.0 ajoutent des surfaces de self-custody et le routage de communauté mondial ; v2.7.0, v2.8.0, v2.9.0, et v2.10.0 superposent des textes clés en anglais simple, les demandes de groupe, l’arbitrage de délai de litige, et la réputation. v3.0.0 lie le paquet ensemble avec des notifications de trade de bout en bout, et v3.1.0 le 9 juin redessine l’écran de trade autour d’une épine dorsale de progression Reserved → Locked → Settled, des cartes d’action colorées par rôle, et une classe d’annonce de vitrine par vendeur (swaps curatés, carnets de prêt, et factures).
Le pivot architectural vit dans v2.0.0. Le format LOCK d’entiercement a changé pour que chaque part d’un split Shamir 2-de-3 soit chiffrée uniquement pour son détenteur (sharePolicy holder-only-v1). L’ecash porteur de la fédération ne se reconstruit plus à partir d’un seul participant seul, fermant un chemin où une partie malveillante avec sa propre part et une part détenue par la fédération pouvait compléter le trade sans consentement. Les clients pré-2.0 échouent bruyamment avec « impossible de trouver votre part » ; le trade ne peut pas se compléter sur un client périmé, et aucun fonds n’est perdu dans le processus. Un verrou v2.0 nécessite chaque partie sur v2.x pour se régler. v2.0.0 a également ajouté les vitrines multi-unités et une vue Market en sats uniquement.
v2.1.0 a introduit la substitution d’arbitre : la part d’arbitre à l’index Shamir 2 est maintenant chiffrée à un ordre de priorité déterministe sur le pool d’arbitres communautaires, afin qu’un arbitre absent puisse être remplacé sans échouer le trade. v2.2.0 a prouvé que la substitution fonctionnait en pratique sur un trade de ₿121 et a ajouté des sauvegardes de substitution de guérison. v2.3.0 a fermé la dernière lacune de front-running d’arbitre en vérifiant l’appartenance communautaire de l’arbitre de l’annonce au moment du verrouillage, et v2.3.1 a fermé la course frère où un slot d’arbitre auto-assigné était un aperçu jusqu’à ce que le verrou les installe.
Les surfaces de self-custody sont arrivées dans v2.4.0 (phrase de récupération BIP-39 pour le portefeuille ecash Fedimint, stockée chiffrée sur Nostr) et v2.5.0 (sauvegarde nsec maître qui possède l’identité Nostr et la graine du portefeuille). v2.6.0 a retravaillé l’onboarding autour d’un sélecteur de communauté global afin que les utilisateurs dans les pays sans Chama locale soient routés vers la fédération la plus proche ; les builds antérieurs faisaient rebondir l’utilisateur sans fallback. v2.7.0 a réécrit l’écran de clé de récupération en anglais simple (« la seule clé de votre compte et de l’argent qui s’y trouve ; Chama ne la voit jamais et ne peut pas la réinitialiser ; si vous la perdez, personne ne peut récupérer votre compte »). v2.8.0 a ajouté les demandes de groupe, le thème sombre/clair, et a ajouté deux nouveaux kinds d’événements (38120 roster, 38121 application). v2.9.0 a changé la résolution de litige à échéance : les trades contestés qui atteignent leur expiration se résolvent maintenant par jugement d’arbitre ; le comportement précédent auto-remboursait. La sortie est marquée COORDINATED afin que toutes les parties en litige doivent mettre à jour. v2.10.0 a ajouté des évaluations pouce-en-haut/pouce-en-bas par trade comme nouveau kind d’événement 38123.
v3.0.0 est le jalon où l’app cesse d’avoir besoin d’une communauté coordinatrice pour fonctionner. Les notifications de trade de bout en bout pingent l’utilisateur uniquement sur les transitions d’état actionnables : la contrepartie a verrouillé les sats, paiement prêt à réclamer, litige nécessite le jugement de l’utilisateur en tant qu’arbitre, ou trade réglé ou expiré. Un toggle dans l’écran Me active ou désactive les notifications, et l’invite de permission ne se déclenche que lorsque le toggle est activé. La déduplication tir-unique empêche un rechargement d’état de déclencher une tempête d’alertes. Un bug de garde-fou wrong-chama a également été fermé dans PR #103, où les versions antérieures pouvaient étiqueter une annonce avec l’étiquette d’une chama mais la fédération d’une autre chama. Les paquets desktop Windows et Linux sont livrés avec la version ; le dmg macOS est retenu jusqu’à ce que la signature et la notarisation atterrissent.
Chama rejoint maintenant Mostro et Shopstr en tant que marketplace Nostr-native, distinguée par une architecture sans serveur, un entiercement Shamir 2-de-3 soutenu par Fedimint, le chiffrement de part uniquement pour le détenteur, et la seule des trois à livrer un client desktop et mobile autonome sans communauté coordinatrice.
Coracle Hosting : service de relais payant plus pile Caravel open source
Le 3 juin, Hodlbod a annoncé Coracle Hosting à hosting.coracle.social, un service de relais communautaire hébergé qui accepte les paiements Lightning récurrents via NWC ou carte. Le service est alimenté par Caravel, le frontend de facturation et de provisionnement de Coracle, et zooid, un runtime de relais qui héberge de nombreux relais virtuels sur une seule machine. Les deux sont open source sur le gitea auto-hébergé de Coracle. Caravel est livré avec une intégration optionnelle livekit et Blossom que les opérateurs peuvent basculer par relais. Un palier gratuit avec des limites de compte de membres permet aux opérateurs d’évaluer le service avant de s’engager sur des détails de paiement.
Hodlbod est franc sur le modèle d’affaires : monétiser l’open source en vendant une version hébergée d’une pile que n’importe qui d’autre peut aussi exécuter. La barrière compétitive est l’intégration Flotilla, qui est la prochaine étape planifiée. Flotilla possède la surface utilisateur, donc l’option hébergée servie depuis l’intérieur de Flotilla devient le chemin par défaut pour tout utilisateur qui préfère l’infrastructure gérée. Hodlbod a offert d’ajouter d’autres opérateurs Caravel au sélecteur d’hébergement alternatif de Flotilla s’ils le contactent, gardant la porte ouverte à un marché d’hébergement fédéré.
Caravel rejoint relay.tools comme plateforme publique de provisionnement de relais Nostr avec des paliers de membres payants. relay.tools précède Caravel et livre en tant que service dominant de création de relais aujourd’hui, avec son propre répertoire de relais communautaires et des flux de jonction de membre payant ou modérateur. La caractéristique distinctive de Caravel est la pile coordonnée : le runtime de relais (zooid), le frontend de facturation et de provisionnement (Caravel lui-même), et le sélecteur côté client (intégration Flotilla, encore en vol) sont livrés comme une seule conception. L’autre caractéristique distinctive est la densité multi-relais-par-processus de zooid, où les relais clients partagent un seul processus hôte afin que l’opérateur amortisse les coûts d’hébergement sur de nombreuses petites communautés. C’est le même argument de densité qui a rendu l’hébergement web partagé viable au début des années 2000, appliqué à la couche de relais de Nostr.
Sorties
Angor v0.2.29 et v0.2.30 : mainnet par défaut et test de financement UAT à 3 utilisateurs
Angor v0.2.29 le 4 juin et v0.2.30 le 8 juin sont les deux sorties de cette semaine pour le protocole de financement Bitcoin-et-Nostr décentralisé. Le changement titre de v0.2.30 est PR #893, qui bascule le réseau par défaut sur mainnet. Angor est toujours livré comme version alpha instable, mais le passage mainnet-par-défaut signale que le protocole a dépassé la phase testnet-uniquement pour les clients desktop et mobile. v0.2.30 atterrit également un flux de création de projet mobile à un seul tap avec upload d’image et réinitialisation de défilement (PR #889) et résout une condition de course où le spinner de facture Lightning pouvait se bloquer (PR #890).
v0.2.29 a ajouté un test UAT de bout en bout dans PR #881 couvrant l’envoi de fonds à 3 utilisateurs sur 10 tours avec dépenses non confirmées, le premier test de flux de financement multi-utilisateur dans la suite de tests Angor. La sortie a également ajouté un plan d’implémentation pour une CLI Angor et un serveur MCP (PR #792), avec des améliorations de CLI pour le workflow de test MCP dans PR #880. PR #885 par DavidGershony a corrigé une facture Lightning Boltz qui utilisait le mauvais réseau après un changement de réseau à l’exécution, un bug qui serait apparu en production après le défaut mainnet v0.2.30. Les paramètres offrent maintenant une purge optionnelle du fichier de portefeuille de récupération pendant l’effacement de données (PR #883).
Sprout v0.3.6 : rafraîchissement TTL de canal éphémère et commandes slash ACP
Sprout v0.3.6, publié le 10 juin, est la huitième version d’une série qui a commencé avec v0.3.7 le 2 juin. Newsletter #25 a couvert la série v0.3.1 à v0.3.6 avec l’intégration mesh-llm et le travail sur les sections de canal ; v0.3.7 à v0.3.15 sont en aval de cela, axés sur le polissage et quelques ajouts visibles par l’utilisateur. Le changement le plus visible par l’utilisateur est un rafraîchissement TTL pour les canaux éphémères dans PR #902 : quand un utilisateur désarchive un canal éphémère, Sprout étend le time-to-live du canal afin que le désarchivage ne le réarchive pas immédiatement sous le minuteur d’expiration original. Les emojis personnalisés mobiles arrivent dans PR #906 aux côtés d’une refonte des paramètres, et les comptes de réactions s’animent maintenant au changement (PR #904).
PR #905 corrige une lacune de longue date où les noms d’affichage à plusieurs mots se cassaient et l’extraction de mention nostr:npub NIP-27 tombait silencieusement. Une UI d’équipe soutenue par répertoire pour desktop est livrée dans PR #912 avec des commandes install, sync et reveal. Les commandes slash passent maintenant à travers les connecteurs ACP dans PR #919, permettant à Sprout de transférer les commandes de style /help directement aux runtimes d’agents tout en gardant l’UI Sprout hors du chemin.
Wisp v1.1.1 : intégration wallet Spark et garde de collage nsec
Wisp v1.1.1, publié le 5 juin, atterrit un écran Connect wallet à deux niveaux avec sous-écran Spark dans PR #548 et parité de tableau de bord avec l’UI du portefeuille iOS dans PR #549. La sortie inclut une garde de collage nsec à l’échelle du système qui détecte un collage préfixé nsec1 n’importe où dans l’app et empêche le champ de l’accepter, fermant l’un des pièges les plus cités dans l’UX Nostr. La connexion par scan QR plus un mode watch-only pour npub et nprofile est livrée dans PR #552, permettant à un utilisateur de parcourir un profil en lecture seule. Les messages de zap se rendent maintenant comme mini-posts dans le tiroir d’engagement (PR #559) afin que les notes de zap portent leur texte aux côtés du montant en sats. Un filtre de web of trust sur les réponses de fil atterrit dans PR #583, permettant aux utilisateurs de masquer le spam de réponse des comptes en dehors de leur graphe de suivi.
Nostria v3.1.46 et nospeak 1.1.3 : refonte des notifications et redémarrage ICE
Nostria v3.1.46 le 7 juin termine une série de trois versions qui a retravaillé le compteur de notifications pour compter uniquement les nouvelles notifications depuis la dernière vue, éliminant une inflation de longue date où le chargement d’anciennes notifications par défilement augmentait le compte du badge. Nostria v3.1.45 a corrigé un bug de paiement partagé affectant les paiements Lightning et code QR et a abandonné une UI translucide précédemment prévue comme non viable sur le compositeur Android.
nospeak v1.1.3 le 4 juin ajoute le redémarrage ICE en état FAILED pour les appels vocaux 1-sur-1. Le comportement WebRTC standard abandonne un appel quand les candidats ICE expirent sans chemin alternatif ; le chemin de redémarrage ICE renégocie les candidats afin que l’appel récupère des changements de NAT ou de réseau transitoires. Les appels Android gardent maintenant l’écran allumé pendant les appels vidéo.
Changements non publiés
Amethyst : 41 PRs poursuivant la voie NIP-32 / NIP-F4 / Tor
Amethyst a fusionné 41 PRs cette semaine sans couper une balise de version, en plus des 52 PRs de la semaine dernière et du travail sur l’étiquetage de hashtag NIP-32 et l’écran de podcast NIP-F4 couverts dans Newsletter #25. La branche active continue à accumuler des fonctionnalités pour la prochaine sortie étiquetée, superposant du polissage sur les ajouts titres de la semaine dernière : découverte d’étiqueteurs de hashtag, écran de podcast, pistes musicales et playlists, watchdog d’auto-guérison Tor, signataires éphémères pour uploads anonymes, et zaps on-chain avec filtrage NIP-05. Le débit de PRs d’Amethyst reste le plus élevé de tout client Nostr, et la file d’attente non publiée est la feuille de route de facto pour ce que les autres clients Android Nostr devront égaler.
Damus : suivi de relais depuis les messages OK et changelog v1.17
Damus PR #3786, fusionnée le 3 juin, ajoute les messages OK réussis d’un relais à la liste de relais post. Les builds Damus antérieurs peuplaient la liste seen-relays uniquement lors de la réception d’un message générique du relais, ce qui signifiait qu’un relais qui accusait le post mais ne livrait aucun événement en retour était invisible pour l’utilisateur. Le changement importe pour les utilisateurs qui veulent confirmer que leur post a atterri sur leur relais d’outbox préféré. PR #3796 corrige un cycle AttributeGraph sur Profile View, et PR #3725 atterrit le changelog v1.17 avant la prochaine sortie étiquetée.
Shopstr : publication double NIP-34
Le dépôt shopstr sur ngit a été annoncé sur Nostr cette semaine comme dépôt git NIP-34, rejoignant les dépôts suivis de ngit. Le dépôt GitHub du client shop reste la surface de développement principale ; l’annonce NIP-34 rend disponible un chemin parallèle de collaboration git-over-Nostr. C’est le deuxième grand projet marketplace Nostr à publier en double sur NIP-34 après Mostro, et continue la migration progressive des métadonnées de projet sur le transport git de Nostr.
Hermes-Marmot : passerelle d’agent IA sur MLS
hermes-marmot, un plugin pour l’Hermes Agent, connecte la surface de messagerie d’un agent IA à des groupes Marmot (MLS-over-Nostr) en utilisant mdk-python, les bindings Python au Rust Marmot Development Kit. Le plugin permet à un utilisateur de DM un agent IA depuis n’importe quel client Nostr qui parle les messages MLS kind 445, y compris Whitenoise. Les DMs entrants utilisent le déballage gift-wrap NIP-59 via les bindings Python nostr-sdk, et les welcomes entrants passent à travers UnwrappedGift.from_gift_wrap à mdk.process_welcome et mdk.accept_welcome. Le contrôle d’accès passe par MARMOT_ALLOWED_USERS (une liste blanche de npubs séparée par des virgules) ou MARMOT_ALLOW_ALL_USERS=true pour un accès dev ouvert.
Le dépôt est nouveau (dernière mise à jour le 27 mai) et petit. Son importance est architecturale : c’est le premier pont public entre un runtime d’agent LLM et un canal de messagerie Nostr chiffré par MLS, et la première utilisation en production de mdk-python au-delà de Whitenoise lui-même. Le modèle pointe vers une communication agent-à-agent où les deux endpoints détiennent des clés MLS et le relais ne voit que le chiffré.
Mises à jour NIP et travail de spécification de protocole
NIP-67 indice de complétude EOSE (PR #2317) fusionné
PR #2317 par mattn a fusionné le 6 juin, ajoutant NIP-67 au protocole. Le NIP étend le message relais EOSE avec un troisième élément optionnel : ["EOSE", <subscription_id>, "finish"] signale que chaque événement stocké correspondant au filtre a été livré, tandis qu’un simple ["EOSE", <subscription_id>] ne porte aucune revendication de complétude. Un relais qui omet l’indice dit au client qu’il pourrait y en avoir plus ; un relais qui omet l’annonce NIP-67 dans NIP-11 conserve le comportement d’aujourd’hui sous l’heuristique legacy existante. Le changement est rétrocompatible dans les deux directions : les clients legacy ignorent l’élément final du tableau, et les relais legacy l’omettent.
La motivation dans la spécification fusionnée est double. Premièrement, la perte silencieuse de données : un client demande les 500 dernières notes contre un relais avec un plafond interne de 300 événements, le relais retourne 300 événements, et le client (utilisant l’heuristique standard received < limit) conclut que le résultat est complet. Les 201e à Ne notes correspondantes les plus anciennes restent sur le relais non lues, avec le client aveugle à ce fait. Deuxièmement, les allers-retours obligatoirement gaspillés : quand un relais plafonne les réponses à 300 événements, tout abonnement qui épuise le plafond nécessite un second REQ avec until=<oldest_created_at> purement pour confirmer la complétude, même quand le filtre correspond exactement à 300 événements. Les deux modes de défaillance sont payés par chaque client sur chaque abonnement à plafond épuisé. L’indice "finish" est une chaîne optionnelle sur un message existant et élimine les deux coûts.
Extension d’autocomplétion NIP-50 (PR #2357) fusionnée
PR #2357 par Alex Gleason a fusionné le 6 juin, ajoutant un token autocomplete:true/false à la recherche NIP-50. L’extension permet à un client de marquer une requête comme recherche typeahead afin que le relais utilise la correspondance de préfixe, avec la recherche full-text comme défaut pour les requêtes sans le token. Le relais de Ditto l’implémente pour les follow packs, les listes, et tout événement avec une balise title, retournant les correspondances contre le préfixe du titre ; le chemin de recherche par défaut exécute le scoring full-text. Sans ce token, les UIs de style autocomplétion n’avaient aucun moyen de communiquer l’intention de recherche par préfixe et les relais devaient deviner à partir de la forme de la requête. Le token est un indice par recherche, pas une capacité à l’échelle du relais, donc un relais peut l’implémenter pour une classe d’événement (titres) sans revendiquer un support d’autocomplétion général.
Alertes d’urgence et diffusions de localisation NIP-GART (PR #2374)
PR #2374 par disinqa, ouverte le 9 juin, définit un format de câble préservant la vie privée sur Nostr pour les alertes d’urgence et les diffusions de localisation adressées à un groupe de destinataires de confiance. Le but de conception déclaré est de cacher l’identité de l’expéditeur, l’appartenance au groupe, et la charge utile aux opérateurs de relais tout en gardant les événements sûrs contre le replay et vérifiables par signature de bout en bout. Le numéro NIP est encore TBD, la proposition est un projet précoce. Le cas d’usage est le modèle standard d’alerte d’urgence : un utilisateur sous menace diffuse un ping de localisation que seul un groupe pré-partagé de contacts de confiance peut déchiffrer, avec le relais aveugle à l’expéditeur, l’ensemble des destinataires, et la charge utile. Les détails du format de câble vivent dans la PR et évolueront probablement à mesure que les mainteneurs révisent.
Méthode de déconnexion NIP-46 (PR #2373)
PR #2373 par hzrd149, ouverte le 8 juin, ajoute une méthode logout à NIP-46 afin qu’un client puisse dire à un bunker explicitement que la session est terminée. Jusqu’à maintenant, la seule façon de terminer une session bunker était d’attendre le timeout de session ou d’arrêter d’utiliser la connexion, les deux laissant le bunker tenant l’état de session pour un client qui est parti. La proposition est courte (une nouvelle méthode) et est le genre de changement de nettoyage qui rend les intégrations bunker de longue durée plus propres.
Proposition hybride relay-P2P NIP-95 circulée en tant que long-form
Une spécification NIP-95 long-form a circulé comme post kind:30023 de npub 91bea5cd9361504c409aaf459516988f68a2fcd482762fd969a7cdc71df4451c le 4 juin sous le titre Protocolo Híbrido Relay-P2P via WebRTC. Le document en portugais définit un protocole hybride pair-à-pair de relais où les clients Nostr se connectent directement les uns aux autres via WebRTC pour la messagerie en direct tout en continuant à utiliser les relais pour la récupération d’événements stockés et la livraison hors ligne. L’auteur a explicitement cadré la spécification comme « LLM-ready », fournissant des définitions de messages, des flux logiques, des schémas de données, et des règles d’état à un niveau de détail qui permet à un modèle IA de générer du code client ou serveur fonctionnel. La proposition n’a pas encore atterri comme PR NIP ; la circulation via kind:30023 est le précurseur habituel d’une pull request formelle nostr-protocol/nips.
NIP-44 v3 gagne un second signataire : Clave porte la spécification
Le déploiement NIP-44 v3 d’Amber v6.2.0 de la semaine dernière a été livré avant toute PR NIPs fusionnée, laissant v3 comme extension spécifique à Amber que d’autres clients devaient refléter pour interopérer. Ce cadrage d’implémentation unique a changé cette semaine. Clave, le signataire iOS remote NIP-46 basé sur push, a atterri un port NIP-44 v3 indépendant les 3 et 4 juin à travers huit commits. Les primitives cryptographiques sont livrées dans trois commits : couche HKDF + clés ECDH, l’algorithme de padding v3, et une API publique de haut niveau plus un Context de chiffrement. En plus de ceux-ci, la surface NIP-46 suit dans le câblage de dispatch RPC à l’intérieur de LightSigner et un schéma PendingRequest qui porte le contexte v3 (kind plus portée), afin que le signataire puisse enregistrer pour quel kind d’événement et cas d’usage la charge utile v3 a été approuvée.
Clave diverge d’Amber sur la surface orientée utilisateur. Un schéma d’octroi de permission avec paliers de sensibilité permet aux utilisateurs d’octroyer le chiffrement v3 pour un kind d’événement et une portée particuliers à un niveau de sensibilité choisi. Lors de la première rencontre, des invites d’approbation conscientes du contexte v3 avec une carte explicative unique introduisent v3 aux utilisateurs. Le travail est dans main et est câblé dans le projet Xcode mais n’est pas publié ; le build étiqueté le plus récent est v0.2.0-build79 du 12 mai.
Deux implémentations indépendantes atterrissent NIP-44 v3 dans les chemins de production avant que la PR NIPs ne fusionne, ce qui renforce le cas pour le format de câble sous-jacent que la PR de protocole formalisera. Les tests d’interop inter-implémentations deviennent maintenant le chemin vers la convergence de spécification, avec la surface d’approbation Android d’Amber et le modèle de palier de sensibilité iOS de Clave comme les deux points de référence. D’autres signataires remote câblant v3 (noauth de nsec.app est dormant depuis mai 2025, et d’autres bunkers n’ont pas annoncé de travail v3) resserreraient encore le consensus.
Activité NIP-34 : Iris adopte la pile avec un nouveau transport hashtree
Iris a publié des annonces de dépôt NIP-34 pour hashtree le 8 juin et iris-apps, iris-drive, et iris-chat-rs le 9 juin, annonçant des URLs de clone sous un nouveau schéma htree:// servi depuis wss://temp.iris.to. Le transport hashtree est une alternative adressée par contenu aux clones routés GRASP, et ces quatre annonces sont ses premières utilisations publiques. Les dépôts portent des descriptions vides et les détails architecturaux émergent encore, mais le choix de publier via annonce NIP-34 (plutôt qu’un manifeste interne Iris personnalisé) signale qu’Iris s’engage envers la pile plus large NIP-34 git-over-Nostr.
NIP deep dive : NIP-67 (Indice de complétude EOSE)
NIP-67 ferme l’une des lacunes de correction les plus anciennes dans NIP-01. La spécification originale définit EOSE comme la frontière entre les événements stockés et les événements d’abonnement en direct pour un REQ, mais elle n’a jamais spécifié si le relais avait fini de livrer toutes les correspondances stockées ou s’était arrêté en cours de route à cause d’un plafond interne. Chaque relais applique un plafond par abonnement (communément 300 à 1000 événements) indépendant du limit du client, et les clients n’ont eu aucun moyen d’observer ce plafond.
La solution de contournement standard était de comparer le compte reçu contre le limit demandé. Si received < limit, traiter le résultat comme complet ; sinon paginer avec until=<oldest_created_at>. Les deux branches sont cassées. La branche received < limit tronque silencieusement : un client demandant 500 notes contre un relais plafonné à 300 voit 300 événements, conclut que le résultat est complet parce que 300 < 500, et ne récupère jamais le reste. Les événements retenus sur le relais ne peuvent pas signaler « plus disponible » à travers un message existant. La pagination comme seconde branche est gaspilleuse : un filtre qui correspond exactement au plafond nécessite un second REQ pour confirmer la complétude, retournant zéro événement tout en consommant un scan de filtre complet sur le relais.
Le correctif de NIP-67 est une chaîne optionnelle sur le message EOSE :
["EOSE", "<sub_id>", "finish"] // explicite : tous les événements stockés livrés
["EOSE", "<sub_id>"] // aucune revendication de complétude
Un relais qui annonce NIP-67 dans les supported_nips NIP-11 et émet un EOSE simple dit au client qu’il y en a plus. Un relais qui omet l’annonce conserve le comportement d’aujourd’hui, et le client revient à l’heuristique existante. Les clients legacy ignorent l’élément final du tableau. La rétrocompatibilité tient dans les deux directions, sans nouveaux verbes ou kinds d’événements.
Ce qui rend NIP-67 digne d’examen est la portée qu’il restreint délibérément. La spécification ne définit aucun curseur ou token de pagination, donc la pagination basée sur until reste le mécanisme. Les plafonds de relais restent là où ils sont, et le NIP ne nécessite aucune exposition de ceux-ci. NIP-67 préserve le sens de EOSE comme frontière stocké-vers-live et ajoute seulement un signal oui-ou-non à la frontière : « J’en ai plus pour vous » versus « c’est tout ». Cette surface minimale est pourquoi la PR a fusionné après une période de revue relativement courte pour une extension NIP-01, et pourquoi mattn note explicitement dans la PR que la traduction IA a été utilisée pour le texte anglais. Le changement est assez petit pour que l’incertitude de traduction n’importe pas.
Exemple d’échange conscient de NIP-67 entre un client et un relais appliquant un plafond. Annonce NIP-11 du relais :
{
"id": "a5f87fe2d4c8b9a0e3f1c4d5e6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f",
"pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"created_at": 1781136000,
"kind": 11,
"tags": [],
"content": "{\"supported_nips\":[1,11,50,67]}",
"sig": "f1e2d3c4b5a6978869504132c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f80192a3b4c5d6e7f80192a3b4c5d6e7f80192a3b4c5d6e7f80192a3b4c5d6e7f80192a3b4c5"
}
L’échange au niveau du câble qui suit :
→ ["REQ", "abc", {"kinds":[1],"limit":500}]
← [...300 messages EVENT...]
← ["EOSE", "abc"] // pas de "finish" : plafond atteint, plus disponible
→ ["REQ", "def", {"kinds":[1],"limit":300,"until":1780900000}]
← [...178 messages EVENT...]
← ["EOSE", "def", "finish"] // complète explicite
La réponse de 178 événements aurait précédemment déclenché un troisième REQ pour confirmer la complétude. Avec NIP-67, le client s’y arrête.
NIP-67 est également notable comme amendement NIP-01 atterrissant avec un consensus rare. La plupart des changements NIP-01 attirent de longs fils de débat parce que la petite surface du protocole est porteuse pour chaque implémentation. NIP-67 a fusionné après une période de revue prolongée (environ sept semaines de l’ouverture à la fusion), suggérant que quand un changement NIP-01 est assez petit et le mode de défaillance assez concret (perte silencieuse de données, aller-retour gaspillé obligatoire), les mainteneurs du protocole sont disposés à étendre le vocabulaire des messages centraux.
NIP deep dive : NIP-50 (Recherche)
NIP-50 définit le champ de filtre search dans les messages REQ, permettant aux clients de demander à un relais de filtrer les événements par correspondance full-text contre une chaîne de requête. La spécification de base fusionnée est délibérément minimale : le champ search est une chaîne, chaque relais décide de sa propre sémantique de recherche (quels champs sont indexés, comment fonctionne le scoring, si le stemming s’applique), et les relais annoncent le support NIP-50 dans leur document NIP-11. Les clients contrôlent l’algorithme de recherche uniquement à travers la chaîne de requête elle-même.
Ce minimalisme est à la fois la force et la contrainte de NIP-50. La force est que n’importe quel relais peut implémenter la recherche à n’importe quel niveau de qualité : un scan de sous-chaîne de base satisfait la spécification, et un relais exécutant Elasticsearch ou Meilisearch la satisfait également. La contrainte est que les clients manquent d’un moyen d’exprimer l’intention de recherche. Une UI de typeahead de mention de profil veut la correspondance de préfixe contre les noms d’affichage ; une recherche de contenu full-text veut le scoring full-text tokenisé à travers le corps de la note. Le même champ search porte les deux, et le relais doit deviner à partir de la forme de la requête.
PR #2357 ajoute le premier token d’extension NIP-50 : autocomplete:true ou autocomplete:false intégré dans la requête de recherche signale quel mode le client veut. Le relais de Ditto implémente le token pour les follow packs, les listes, et tout événement avec une balise title, passant à la correspondance de préfixe quand autocomplete:true est présent. Le token vit inline dans la requête (les champs de filtre séparés restent intacts), donc il voyage avec la chaîne de recherche et ne nécessite aucun bump de protocole de câble :
search: "fiat autocomplete:true"
Les indices en forme de token comme celui-ci sont la façon dont NIP-50 a toujours géré les dialectes spécifiques au relais. Les relais supportaient déjà des tokens comme language:en et domain:example.com. Chacun reste spécifique au relais, chaque relais documentant son propre dialecte. La PR #2357 de NIP-50 élève autocomplete d’un token privé au relais à un béni par la spécification, ouvrant la voie à une recherche consciente du typeahead à travers les relais.
Exemple REQ NIP-50 avec le token autocomplete, ciblant un relais qui indexe les titres de profil kind 0 :
{
"id": "b7c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f7081a2b3c4d5e6f70819a2b3c4d5",
"pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"created_at": 1781136000,
"kind": 1,
"tags": [
["client", "example-mention-picker"]
],
"content": "Sent search: kinds=[0], search=\"fiat autocomplete:true\", limit=10",
"sig": "12d3e4f5061728394a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f7081a2b3c4d5e6f70819a2b3c4d5e6f7081a2b3c4d5e6f708192"
}
Le REQ réel au niveau du câble :
["REQ", "mention-picker", {"kinds":[0],"search":"fiat autocomplete:true","limit":10}]
Un relais qui ne reconnaît pas le token traite autocomplete:true comme faisant partie de la chaîne de recherche littérale et revient à la correspondance full-text, retournant des résultats corrects (bien que classés différemment). La dégradation gracieuse rend le token sûr à inclure inconditionnellement pour les clients qui préfèrent la correspondance de préfixe quand disponible.
La prochaine extension NIP-50 probable est le contrôle de classement par kind : un indice qui dit « classer par created_at descendant » versus le score de pertinence par défaut. Plusieurs relais acceptent déjà sort:newest comme token privé au relais, et le même chemin d’élévation qui a amené autocomplete dans la spécification s’applique. La recherche reste l’une des rares primitives Nostr où les relais rivalisent sur la qualité des résultats ; la fiabilité de la livraison est la même à travers tous les relais conformes. Les tokens incrémentaux permettent aux clients d’exploiter cette compétition de qualité sans forcer les relais à livrer une nouvelle spécification lourde.