diff --git a/README.md b/README.md
index dce91b635..7fda6ae95 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [ ] Relay-based Groups (NIP-29)
- [x] Custom Emoji (NIP-30)
- [x] Event alt descriptors (NIP-31)
-- [ ] Labeling (NIP-32)
+- [x] Labeling (NIP-32)
- [x] Git Stuff (NIP-34)
- [x] Torrents (NIP-35)
- [x] Sensitive Content (NIP-36)
diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml
index 0f96a3028..607f92a46 100644
--- a/amethyst/src/main/res/values-hu-rHU/strings.xml
+++ b/amethyst/src/main/res/values-hu-rHU/strings.xml
@@ -387,6 +387,9 @@
Hozzáadás a nyilvános könyvjelzőkhözTörlés a privát könyvjelzőkbőlTörlés a nyilvános könyvjelzőkből
+ Rögzített bejegyzések
+ Rögzítés a profilhoz
+ Rögzítés megszüntetése a profiltólKönyvjelzőlistákKönyvjelzőlista ikonjaÚj könyvjelzőlista
@@ -719,6 +722,17 @@
Általános szerződési feltételekNem érhető elHibák és megjegyzések ettől az átjátszótól
+ Átjátszófigyelési jelentések
+ Megnyitás
+ Olvasás
+ Írás
+ RTT (válaszidő)
+ Hálózat
+ Típus
+ Támogatott NIP-ek
+ Követelmények
+ Utoljára ellenőrizve
+ %1$d msAktív előfizetésekFüggőben lévő kimenő eseményekSzükséges előfizetések (%1$d)
@@ -1655,8 +1669,12 @@
nincsenek eseményekBitcoin felfedező (OTS)események
+ Öntől származó események
+ Önnek szóló események
+ kereshető eseményekKözvetlen üzenetekprofilok
+ kimenő átjátszók listájaátjátszóbeállításokUtoljára %1$s ezelőtt látták<%1$s
@@ -1721,5 +1739,94 @@
Törli ezt a webes könyvjelzőt?Webcím megnyitása
+ Ez a gyűjtési cél lezárult
+ %1$s gyűlt össze a(z) %2$s satoshis célból
+ Célösszeg (satoshiban)
+ 100 000
+ Cél leírása, bemutatása
+ Mire gyűjt adományokat?
+ Rövid összefoglalás
+ Rövid leírás, amely megjelenik az előnézetekben
+ Kép webcíme (nem kötelező)
+ https://pelda.hu/kep.jpg
+ Weboldal webcíme (nem kötelező)
+ https://pelda.hu
+ Határidő (nem kötelező)
+ Határidő beállítása
+ Új cél
+ Cél létrehozása
+ Törlési kérés
+ Kérés az átjátszóknak, hogy a kiválasztott dátumig visszamenőleg véglegesen töröljék az Ön összes adatát. Ez a művelet a NIP-62 szabványon alapul, és egyes joghatóságokban jogilag kötelező érvényű.
+ Egy átjátszó kiválasztása
+ Célátjátszók
+ Összes átjátszó
+ Ezzel az ÖSSZES átjátszót arra kéri, hogy a kiválasztott dátumig töröljön mindent, ami az Ön kulcsához kapcsolódik. Ezt az eseményt a lehető legszélesebb körben közzétesszük. Ez a művelet nem vonható vissza.
+ Adatok törlése eddig:
+ Az ezen időpont előtt létrehozott összes esemény törlését kérelmezi a kiválasztott átjátszótól.
+ Indoklás (nem kötelező)
+ Indoklás vagy jogi nyilatkozat az átjátszó üzemeltetőjének
+ Törlési kérés küldése
+ Törlési kérés megerősítése
+ Ön arra kéri a(z) %1$s átjátszót, hogy a kiválasztott dátum előtt létrehozott összes adatát végleges törölje. Ez a művelet nem vonható vissza.
+ Ön arra kéri az ÖSSZES átjátszót, hogy a kiválasztott dátum előtt létrehozott összes adatát végleges törölje. Ez a művelet közvetítve lesz mindenhová és nem vonható vissza.
+ Törlési kérés elküldve
+ Dátum kiválasztása
+ Idő kiválasztása
+ Törlési előzmények
+ Frissítés
+ Ezek az Ön korábbi, a kapcsolódott átjátszókon talált törlési kérései. Az ezekben az eseményekben megjelölt átjátszók nem tárolhatják az Ön adatait az esemény dátuma előtti időszakból.
+ Nem találhatók törlési kérések
+ Ön még nem küldött törlési kérést.
+ Célátjátszók
+ Ez a kérés érinti az összes átjátszót. Használja a törlési kérés menüjét az egyes átjátszók megfelelőségének teszteléséhez.
+ Tesztelés
+ Megfelelő
+ Nem megfelelő
+ Hiba
+ Törlési előzmények
+ %1$s kezelése
+ Átjátszókezelési funkciók betöltése…
+ Nem sikerült kapcsolódni az átjátszókezeléshez
+ Nem érhető el kezelési eljárás
+ Elvetés
+ Felhasználók
+ Események
+ Típusok
+ IP-címek
+ Beállítások
+ Letiltott felhasználók
+ Nincsenek letiltott felhasználók
+ Engedélyezett felhasználók
+ Nincsenek engedélyezett felhasználók
+ Moderációs várólista
+ Nincsenek moderálandó események
+ Letiltott események
+ Nincsenek letiltott események
+ Engedélyezett típusok
+ Nincsenek engedélyezett típusok
+ Letiltott IP-címek
+ Nincsenek letiltott IP-címek
+ Hozzáadás
+ Eltávolítás
+ Engedélyezés
+ Letiltás
+ Nyilvános kulcs letiltása
+ Nyilvános kulcs engedélyezése
+ Esemény letiltása
+ Típus engedélyezése
+ IP-cím letiltása
+ Nyilvános kulcs (hex)
+ Eseményazonosító (hex)
+ Típus száma
+ IP-cím
+ Indoklás (nem kötelező)
+ Megerősítés
+ Mégse
+ Alkamaz
+ Átjátszó neve
+ Átjátszó leírása
+ Átjátszó ikonjának webcíme
+ Átjátszó kezelése
+ Kezelés
diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml
index a646715c6..183489745 100644
--- a/amethyst/src/main/res/values-pl-rPL/strings.xml
+++ b/amethyst/src/main/res/values-pl-rPL/strings.xml
@@ -384,6 +384,9 @@
Dodaj do publicznych zakładekUsuń z prywatnych zakładekUsuń z publicznych zakładek
+ Przypięte notatki
+ Przypnij do profilu
+ Odepnij z profiluListy zakładekIkona listy zakładekNowa lista zakładek
@@ -716,6 +719,16 @@
Zasady i warunki użytkowaniaNie dotyczyBłędy i powiadomienia z tego transmitera
+ Raporty z monitoringu transmitera
+ Otwórz
+ Odczyt
+ Zapis
+ Sieć
+ Typ
+ Obsługiwane Nipy
+ Wymagania
+ Ostatnie sprawdzenie
+ %1$d msAktywne subskrypcjeWydarzenia oczekujące na wysłanieREQ Subskrypcje (%1$d)
@@ -1652,6 +1665,7 @@
wydarzeńDMsprofile
+ lista transmiterów wysyłającychustawienia transmiterówOstatnio widziano %1$s temu<%1$s
@@ -1716,5 +1730,56 @@
Usunąć tę zakładkę?Otwórz adres URL
+ Ta zbiórka została zamknięta
+ Sfinansowano %1$s z %2$s satoszów
+ Kwota zbiorki (w satoszach)
+ 100000
+ Opisz cel zbiórki
+ Na co zbierasz fundusze?
+ Krótkie podsumowanie
+ Krótki opis wyświetlany w podglądzie
+ Adres URL zdjęcia (opcjonalnie)
+ https://domena.pl/img.jpg
+ Adres strony (opcjonalnie)
+ https://domena.pl
+ Termin ukończenia (opcjonalnie)
+ Ustaw ostateczny termin
+ Nowy cel zbiórki
+ Utwórz zbiórkę
+ Prośba o usunięcie
+ Poproś transmitery o całkowite usunięcie wszystkich Twoich danych z okresu do wybranej daty. Działanie to opiera się na standardzie NIP-62 i w niektórych jurysdykcjach ma moc prawną.
+ Wybierz transmiter
+ Transmitery docelowe
+ WSZYSTKIE TRANSMITERY
+ Spowoduje to wysłanie żądania do WSZYSTKICH przekaźników o usunięcie wszystkich danych powiązanych z Twoim kluczem do wybranej daty. Informacja ta zostanie rozpowszechniona tak szeroko, jak to tylko możliwe. Czynności tej nie można cofnąć.
+ Usuń dane aż do
+ Wszystkie wydarzenia utworzone przed tą datą zostaną zgłoszone do usunięcia z wybranego transmitera.
+ Powód (Opcjonalne)
+ Uzasadnienie lub informacja prawna dla operatora transmitera
+ Wyślij prośbę o usunięcie
+ Potwierdź żądanie usunięcia
+ Zamierzasz poprosić %1$s o trwałe usunięcie wszystkich danych utworzonych przed wybraną datą. Czynności tej nie można cofnąć.
+ Za chwilę wyślesz żądanie do KAŻDEGO transmitera, aby trwale usunął wszystkie Twoje dane utworzone przed wybraną datą. Komunikat ten zostanie rozesłany wszędzie i nie będzie można go cofnąć.
+ Wysłano prośbę o usunięcie
+ Wybierz datę
+ Wybierz godzinę
+ Wyczyść historię
+ Odśwież
+ Oto Twoje wcześniejsze prośby o usunięcie danych znalezione na podłączonych transmiterach. Transmitery oznaczone w tych wydarzeniach nie powinny przechowywać żadnych Twoich danych sprzed daty wydarzenia.
+ Nie znaleziono wniosków o usunięcie
+ Nie wysłano jeszcze żadnych żądań do usunięcia wydarzeń.
+ Transmitery docelowe
+ To żądanie dotyczy wszystkich transmiterów. Aby sprawdzić zgodność poszczególnych transmiterów, skorzystaj z opcji \"Prośba o usunięcie\".
+ Test
+ Zgodny
+ Niezgodny
+ Błąd
+ Wyczyść historię
+ Zarządzaj %1$s
+ Wczytywanie funkcji zarządzania transmiterem…
+ Brak dostępnych metod zarządzania
+ Ignoruj
+ Zarządzaj Transmiterem
+ Zarządzaj
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/EventExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/EventExt.kt
new file mode 100644
index 000000000..5a1adc81a
--- /dev/null
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/EventExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip32Labeling
+
+import com.vitorpamplona.quartz.nip01Core.core.Event
+import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag
+import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag
+
+/**
+ * NIP-32 self-reporting: `l` and `L` tags MAY be added to any event kind.
+ * For non-1985 events, labels refer to the event itself.
+ */
+fun Event.selfReportLabels() = tags.mapNotNull(LabelTag::parse)
+
+fun Event.selfReportNamespaces() = tags.mapNotNull(LabelNamespaceTag::parse)
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/LabelEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/LabelEvent.kt
new file mode 100644
index 000000000..9615773a0
--- /dev/null
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/LabelEvent.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2025 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip32Labeling
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.Event
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
+import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
+import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
+import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
+import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
+import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
+import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
+import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
+import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
+import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
+import com.vitorpamplona.quartz.nip01Core.tags.events.eTag
+import com.vitorpamplona.quartz.nip01Core.tags.hashtags.HashtagTag
+import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
+import com.vitorpamplona.quartz.nip01Core.tags.people.pTag
+import com.vitorpamplona.quartz.nip31Alts.alt
+import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag
+import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag
+import com.vitorpamplona.quartz.utils.TimeUtils
+
+/**
+ * NIP-32: Label Event (kind 1985)
+ *
+ * Attaches labels to existing events, pubkeys, relays, or topics.
+ * Supports distributed moderation, collection management, license assignment,
+ * and content classification.
+ */
+@Immutable
+class LabelEvent(
+ id: HexKey,
+ pubKey: HexKey,
+ createdAt: Long,
+ tags: Array>,
+ content: String,
+ sig: HexKey,
+) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
+ EventHintProvider,
+ PubKeyHintProvider,
+ AddressHintProvider {
+ override fun pubKeyHints(): List = tags.mapNotNull(PTag::parseAsHint)
+
+ override fun linkedPubKeys(): List = tags.mapNotNull(PTag::parseKey)
+
+ override fun eventHints(): List = tags.mapNotNull(ETag::parseAsHint)
+
+ override fun linkedEventIds(): List = tags.mapNotNull(ETag::parseId)
+
+ override fun addressHints(): List = tags.mapNotNull(ATag::parseAsHint)
+
+ override fun linkedAddressIds(): List = tags.mapNotNull(ATag::parseAddressId)
+
+ /** All label namespace (`L`) tags on this event. */
+ fun namespaces() = tags.mapNotNull(LabelNamespaceTag::parse)
+
+ /** All label (`l`) tags on this event. */
+ fun labels() = tags.mapNotNull(LabelTag::parse)
+
+ /** Labels filtered by a specific namespace. */
+ fun labelsByNamespace(namespace: String) = labels().filter { it.namespace == namespace }
+
+ /** Referenced event IDs (label targets). */
+ fun labeledEvents() = tags.mapNotNull(ETag::parseId)
+
+ /** Referenced pubkeys (label targets). */
+ fun labeledPubKeys() = tags.mapNotNull(PTag::parseKey)
+
+ /** Referenced addresses (label targets). */
+ fun labeledAddresses() = tags.mapNotNull(ATag::parseAddressId)
+
+ /** Referenced hashtags/topics (label targets via `t` tag). */
+ fun labeledHashtags() = tags.mapNotNull(HashtagTag::parse)
+
+ /** Referenced relay URLs (label targets via `r` tag). */
+ fun labeledRelayUrls(): List =
+ tags
+ .filter { it.size >= 2 && it[0] == "r" && it[1].isNotEmpty() }
+ .map { it[1] }
+
+ companion object {
+ const val KIND = 1985
+ const val ALT = "Label event"
+
+ /**
+ * Build a label event for labeling events.
+ */
+ fun buildEventLabel(
+ labeledEventId: HexKey,
+ labeledEventRelay: String? = null,
+ labeledEventAuthor: HexKey? = null,
+ labels: List,
+ content: String = "",
+ createdAt: Long = TimeUtils.now(),
+ ) = eventTemplate(KIND, content, createdAt) {
+ alt(ALT)
+ eTag(ETag(labeledEventId, labeledEventRelay?.let { RelayUrlNormalizer.normalizeOrNull(it) }, labeledEventAuthor))
+ labels.map { it.namespace }.distinct().forEach { labelNamespace(it) }
+ labels.forEach { label(it) }
+ }
+
+ /**
+ * Build a label event for labeling pubkeys.
+ */
+ fun buildPubKeyLabel(
+ labeledPubKey: HexKey,
+ labeledPubKeyRelay: String? = null,
+ labels: List,
+ content: String = "",
+ createdAt: Long = TimeUtils.now(),
+ ) = eventTemplate(KIND, content, createdAt) {
+ alt(ALT)
+ pTag(labeledPubKey, labeledPubKeyRelay?.let { RelayUrlNormalizer.normalizeOrNull(it) })
+ labels.map { it.namespace }.distinct().forEach { labelNamespace(it) }
+ labels.forEach { label(it) }
+ }
+
+ /**
+ * Build a label event with custom tag targets and labels.
+ */
+ fun build(
+ labels: List,
+ content: String = "",
+ createdAt: Long = TimeUtils.now(),
+ ) = eventTemplate(KIND, content, createdAt) {
+ alt(ALT)
+ labels.map { it.namespace }.distinct().forEach { labelNamespace(it) }
+ labels.forEach { label(it) }
+ }
+ }
+}
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/TagArrayBuilderExt.kt
new file mode 100644
index 000000000..f8830b89f
--- /dev/null
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/TagArrayBuilderExt.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip32Labeling
+
+import com.vitorpamplona.quartz.nip01Core.core.Event
+import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
+import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag
+import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag
+
+/** Adds a label (`l`) tag. */
+fun TagArrayBuilder.label(tag: LabelTag) = add(tag.toTagArray())
+
+/** Adds a label (`l`) tag with explicit label and namespace values. */
+fun TagArrayBuilder.label(
+ label: String,
+ namespace: String,
+) = add(LabelTag.assemble(label, namespace))
+
+/** Adds a label namespace (`L`) tag. */
+fun TagArrayBuilder.labelNamespace(namespace: String) = add(LabelNamespaceTag.assemble(namespace))
+
+/** Adds a label namespace (`L`) tag. */
+fun TagArrayBuilder.labelNamespace(tag: LabelNamespaceTag) = add(tag.toTagArray())
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelNamespaceTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelNamespaceTag.kt
new file mode 100644
index 000000000..fdeb864fc
--- /dev/null
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelNamespaceTag.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2025 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip32Labeling.tags
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
+/**
+ * NIP-32: `L` tag — label namespace.
+ *
+ * Format: `["L", ""]`
+ *
+ * Namespaces SHOULD be unambiguous (ISO standard or reverse domain name notation).
+ * The special `ugc` namespace MAY be used when label content is provided by an end user.
+ * `L` tags starting with `#` indicate that the label target should be associated
+ * with the label's value (attaching standard nostr tags to events, pubkeys, etc.).
+ */
+@Immutable
+data class LabelNamespaceTag(
+ val namespace: String,
+) {
+ fun toTagArray() = assemble(namespace)
+
+ /**
+ * Returns true if this namespace is a tag-association namespace (starts with `#`).
+ * When `L` = `#t`, an `l` tag like `["l", "bitcoin", "#t"]` means the target
+ * should be associated with the hashtag `bitcoin`.
+ */
+ fun isTagAssociation() = namespace.startsWith("#")
+
+ companion object {
+ const val TAG_NAME = "L"
+
+ fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
+
+ fun isTagged(
+ tag: Array,
+ namespace: String,
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == namespace
+
+ fun parse(tag: Array): LabelNamespaceTag? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+
+ return LabelNamespaceTag(tag[1])
+ }
+
+ fun parseNamespace(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
+
+ fun assemble(namespace: String) = arrayOf(TAG_NAME, namespace)
+ }
+}
diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelTag.kt
new file mode 100644
index 000000000..807480240
--- /dev/null
+++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelTag.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2025 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip32Labeling.tags
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
+/**
+ * NIP-32: `l` tag — label value with an optional namespace mark.
+ *
+ * Format: `["l", "