finish skills

This commit is contained in:
nrobi144
2025-12-30 15:45:02 +02:00
parent f654af9d8a
commit f180fd39e1
18 changed files with 5287 additions and 42 deletions
@@ -0,0 +1,293 @@
# Event Hierarchy & Structure
## Core Hierarchy
```
IEvent (empty interface)
└── Event (@Immutable base class)
├── BaseAddressableEvent (replaceable + addressable, has d-tag)
│ ├── BaseReplaceableEvent (kinds 10000-20000, FIXED_D_TAG = "")
│ └── [Specific addressable events - 30000-40000]
└── [Specific event implementations - all other kinds]
```
## Event Base Class
**Location**: `/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Event.kt`
```kotlin
@Immutable
open class Event(
val id: HexKey, // SHA-256 hash of serialized event
val pubKey: HexKey, // Author's public key (32 bytes hex)
val createdAt: Long, // Unix timestamp
val kind: Kind, // Event kind (Int typealias)
val tags: TagArray, // Array of tag arrays
val content: String, // Event content
val sig: HexKey, // schnorr signature (64 bytes hex)
) : IEvent, OptimizedSerializable
```
## Kind Classification
```kotlin
typealias Kind = Int
fun Kind.isEphemeral() = this in 20000..29999
fun Kind.isReplaceable() = this == 0 || this == 3 || this in 10000..19999
fun Kind.isAddressable() = this in 30000..39999
fun Kind.isRegular() = this in 1000..9999
```
## Common Event Types
### Text Note (kind 1)
```kotlin
class TextNoteEvent(...) : BaseThreadedEvent(...),
EventHintProvider, AddressHintProvider, PubKeyHintProvider, SearchableEvent
// Threading support via markers: reply, root, mention
fun replyTo(): List<Note> // Direct reply targets
fun root(): Note? // Root of thread
```
### Metadata (kind 0)
```kotlin
class MetadataEvent(...) : BaseAddressableEvent(...)
// Replaceable: newest version overwrites old
// d-tag automatically set to "" for kind 0
fun name(): String?
fun displayName(): String?
fun picture(): String?
fun about(): String?
fun lnAddress(): String?
```
### Reaction (kind 7)
```kotlin
class ReactionEvent(...) : Event(...)
companion object {
const val LIKE = "+"
const val DISLIKE = "-"
fun like(reactedTo: EventHintBundle<Event>, ...)
fun dislike(reactedTo: EventHintBundle<Event>, ...)
}
```
### Zap Request/Receipt (kinds 9734, 9735)
```kotlin
class LnZapRequestEvent(...) : Event(...)
// Created by client, sent to Lightning Address
class LnZapEvent(...) : Event(...)
// Receipt from LSP, contains bolt11 + embedded zap request
val zapRequest: LnZapRequestEvent? by lazy { containedPost() }
val amount: BigDecimal? by lazy { /* parse from bolt11 */ }
```
### Long-Form Content (kind 30023)
```kotlin
class LongTextNoteEvent(...) : BaseAddressableEvent(...)
// Blog posts, articles
// Addressable via kind:pubkey:d-tag
```
### Lists (kinds 10000-30004)
```kotlin
sealed class PeopleListEvent : BaseAddressableEvent {
object MuteList : PeopleListEvent(10000)
object PinList : PeopleListEvent(10001)
object BookmarkList : PeopleListEvent(10003)
// ... 18 list types total
}
```
## Event Interfaces
### Hint Providers
Events can implement interfaces to optimize relay queries:
```kotlin
interface EventHintProvider {
fun taggedEventIds(): Set<HexKey>
fun taggedEventRelays(): Map<HexKey, Set<NormalizedRelayUrl>>
}
interface PubKeyHintProvider {
fun taggedPubKeys(): Set<HexKey>
fun taggedPubKeyRelays(): Map<HexKey, Set<NormalizedRelayUrl>>
}
interface AddressHintProvider {
fun taggedAddresses(): Set<Address>
fun taggedAddressRelays(): Map<Address, Set<NormalizedRelayUrl>>
}
interface SearchableEvent {
fun subject(): String?
fun isContentEncoded(): Boolean
}
```
## Event Building Pattern
### DSL Builder
```kotlin
TextNoteEvent.build(
note = "Hello Nostr",
replyingTo = eventBundle,
createdAt = TimeUtils.now()
) {
pTag(pubKey, relayHint) // Tag person
eTag(eventId, relayHint, "reply") // Tag event with marker
hashtag("nostr") // Add hashtag
alt("A short note") // Alt text
}
```
### Event Template (Low-level)
```kotlin
suspend fun eventTemplate(
kind: Kind,
content: String,
createdAt: Long,
initializer: TagArrayBuilder.() -> Unit
): EventTemplate {
val tags = TagArrayBuilder().apply(initializer).build()
return EventTemplate(kind, tags, content, createdAt)
}
// Sign with signer
val template = eventTemplate(1, "Hello", now()) { pTag(pubkey) }
val signedEvent = signer.sign(template)
```
## Addressable vs Regular Events
| Feature | Regular Event | Addressable Event |
|---------|---------------|-------------------|
| **Identifier** | Event ID (SHA-256 hash) | Address (kind:pubkey:d-tag) |
| **Replaceability** | Immutable | Newest replaces old |
| **d-tag** | Optional | Required |
| **Lookup** | By event ID | By address |
| **Example** | Text note (kind 1) | Metadata (kind 0), Long-form (kind 30023) |
```kotlin
// Regular event address
note = LocalCache.getNoteIfExists(eventId)
// Addressable event address
address = Address(kind = 30023, pubkey = authorHex, dTag = "my-article")
note = LocalCache.getAddressableNoteIfExists(address)
```
## Event Validation
```kotlin
// Verify event ID matches computed hash
fun Event.verifyId(): Boolean =
EventHasher.hashIdCheck(id, pubKey, createdAt, kind, tags, content)
// Verify signature
fun Event.verifySignature(): Boolean =
Nip01.verify(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))
// Complete verification
fun Event.checkSignature() {
if (!verifyId()) throw Exception("ID mismatch")
if (!verifySignature()) throw Exception("Bad signature!")
}
```
## Event Serialization
```kotlin
// To JSON (for transmission/signing)
fun Event.toJson(): String = OptimizedJsonMapper.toJson(this)
// From JSON
fun Event.fromJson(json: String): Event = OptimizedJsonMapper.fromJson(json)
// Event ID generation (SHA-256 of canonical JSON)
fun EventHasher.hashId(
pubKey: HexKey,
createdAt: Long,
kind: Kind,
tags: TagArray,
content: String
): HexKey {
val serialized = """[0,"$pubKey",$createdAt,$kind,${tags.toJson()},"$content"]"""
return sha256(serialized.encodeToByteArray()).toHexKey()
}
```
## Event Lifecycle in LocalCache
```
Event received from relay
LocalCache.consume(event, relay, wasVerified)
getOrCreateNote(event.id) or getOrCreateAddressableNote(address)
justVerify(event) → checkSignature()
note.loadEvent(event, author, replyTo)
Update indices (replies, reactions, boosts)
refreshNewNoteObservers(note) → emit to SharedFlow
UI updates
```
## Common Event Patterns
### Reply Threading
```kotlin
// Root event (top of thread)
val rootEvent = TextNoteEvent.build("Thread root") { }
// Reply to root
val reply1 = TextNoteEvent.build("First reply", replyingTo = rootEvent) {
// Automatically adds:
// ["e", <root_id>, <relay>, "root"]
// ["e", <root_id>, <relay>, "reply"]
}
// Reply to reply (nested)
val reply2 = TextNoteEvent.build("Nested reply", replyingTo = reply1) {
// Automatically adds:
// ["e", <root_id>, <relay>, "root"]
// ["e", <reply1_id>, <relay>, "reply"]
}
```
### Replaceable Events
```kotlin
// Metadata update (kind 0) - newest wins
val metadata1 = MetadataEvent.createNew(name = "Alice", picture = "url1")
Thread.sleep(1000)
val metadata2 = MetadataEvent.createNew(name = "Alice Updated", picture = "url2")
// LocalCache keeps only metadata2 (higher createdAt)
```
### Event Deletion
```kotlin
// Delete events
val deletion = DeletionEvent.create(
deleteEvents = listOf(eventId1, eventId2),
reason = "Spam",
signer = signer
)
// LocalCache marks events as deleted, but doesn't remove (for verification)
```
## 63+ Event Classes
Full list at `/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip*/` - one class per event type across 60+ NIP implementations.
@@ -0,0 +1,179 @@
# NIP Catalog: 60 Standard + 8 Experimental NIPs in Quartz
## Standard NIPs by Category
### Core/Basic Protocol
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 01 | `nip01Core/` | Event.kt, Kind.kt, Tag.kt | Core protocol, event structure, kinds, tags |
| 02 | `nip02FollowList/` | ContactListEvent.kt | Follow/contact lists (kind 3) |
| 03 | `nip03Timestamp/` | OpenTimestampsAttestation.kt | Timestamps |
| 04 | `nip04Dm/` | EncryptedDmEvent.kt | Legacy encrypted DMs (deprecated for NIP-17) |
| 05 | `nip05DnsIdentifiers/` | Nip05Verifier.kt | DNS-based verification |
| 06 | `nip06KeyDerivation/` | Mnemonic-related | BIP-39 key derivation |
| 09 | `nip09Deletions/` | DeletionEvent.kt | Event deletion requests (kind 5) |
| 11 | `nip11RelayInfo/` | RelayInformation.kt | Relay metadata |
| 13 | `nip13Pow/` | ProofOfWork.kt | Proof of work |
| 14 | `nip14Subject/` | Subject tags | Subject tags for text notes |
| 17 | `nip17Dm/` | GiftWrapEvent.kt, SealedGossipEvent.kt | Private DMs (replacem
ent for NIP-04) |
| 21 | `nip21UriScheme/` | URI scheme (`nostr:`) | URI scheme parsing |
| 42 | `nip42RelayAuth/` | RelayAuthEvent.kt | Relay authentication (kind 22242) |
| 44 | `nip44Encryption/` | Nip44.kt, Nip44v2.kt | Modern encryption (ChaCha20) |
| 49 | `nip49PrivKeyEnc/` | NIP-49Ncryptsec.kt | Private key encryption format |
### Content Types
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 10 | `nip10Notes/` | TextNoteEvent.kt | Text notes with threading (kind 1) |
| 18 | `nip18Reposts/` | RepostEvent.kt, GenericRepostEvent.kt | Reposts (kind 6, 16) |
| 22 | `nip22Comments/` | CommentEvent.kt | Comments (kind 1111) |
| 23 | `nip23LongContent/` | LongTextNoteEvent.kt | Long-form content (kind 30023) |
| 25 | `nip25Reactions/` | ReactionEvent.kt | Reactions (kind 7) |
| 31 | `nip31Alts/` | Alt tags | Alt description tags |
| 36 | `nip36SensitiveContent/` | Content warnings | Content warning tags |
| 37 | `nip37Drafts/` | DraftEvent.kt | Drafts (kind 31234) |
| 50 | `nip50Search/` | Search filters | Full-text search |
### Encoding & Standards
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 19 | `nip19Bech32/` | Nip19.kt | Bech32 encoding (npub, nsec, note, nevent, nprofile, naddr) |
| 40 | `nip40Expiration/` | Expiration tags | Event expiration |
| 48 | `nip48ProxyTags/` | Proxy tags | Proxy tags for delegation |
| 62 | `nip62RequestToVanish/` | RequestToVanishEvent.kt | Request to vanish (kind 12) |
| 98 | `nip98HttpAuth/` | HTTP authorization | HTTP auth header |
### Lists & Management
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 51 | `nip51Lists/` | 18 list types | Named lists (mute, bookmarks, pins, communities, etc.) (kinds 10000-30004) |
| 65 | `nip65RelayList/` | AdvertisedRelayListEvent.kt | Relay lists (kind 10002) |
### Social & Identity
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 39 | `nip39ExtIdentities/` | External identities | External identity claims |
| 46 | `nip46RemoteSigner/` | NostrConnectEvent.kt | Remote signer protocol (bunker) |
| 47 | `nip47WalletConnect/` | Nostr Wallet Connect | Wallet connection protocol |
| 56 | `nip56Reports/` | ReportEvent.kt | Reports (kind 1984) |
| 57 | `nip57Zaps/` | LnZapEvent.kt, LnZapRequestEvent.kt | Lightning zaps (kinds 9734, 9735) |
| 58 | `nip58Badges/` | Badge events | Badge definitions & awards (kinds 30009, 8) |
| 59 | `nip59Giftwrap/` | GiftWrapEvent.kt | Gift-wrapped events for privacy |
| 75 | `nip75ZapGoals/` | ZapGoalEvent.kt | Zap goals (kind 9041) |
### Specialized Content
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 28 | `nip28PublicChat/` | ChannelCreateEvent.kt, ChannelMessageEvent.kt | Public chat channels (kinds 40-44) |
| 30 | `nip30CustomEmoji/` | EmojiUrl.kt | Custom emoji |
| 34 | `nip34Git/` | Git patch/issue events | Git repository tracking (kinds 30617, 30618, 1617, 1621, 1622, 1630, 1633) |
| 35 | `nip35Torrents/` | Torrent events | Torrent tracking |
| 52 | `nip52Calendar/` | Calendar events | Calendar time-based/date-based (kinds 31922-31925) |
| 53 | `nip53LiveActivities/` | LiveActivitiesEvent.kt | Live events/streaming (kind 30311) |
| 54 | `nip54Wiki/` | WikiNoteEvent.kt | Wiki pages (kind 30818) |
| 68 | `nip68Picture/` | Picture metadata | Picture metadata |
| 71 | `nip71Video/` | 7 video event types | Video events (kinds 34235, 35235, 1234, 1235) |
| 72 | `nip72ModCommunities/` | Community events | Moderated communities (kinds 34550, 34551, 9041) |
| 84 | `nip84Highlights/` | HighlightEvent.kt | Highlights (kind 9802) |
| 89 | `nip89AppHandlers/` | AppDefinitionEvent.kt | App recommendations (kinds 31990, 31989) |
| 90 | `nip90Dvms/` | DVM job events | Data Vending Machines (DVMs) (kinds 5000-7000) |
| 92 | `nip92IMeta/` | IMeta tags | Image metadata tags |
| 94 | `nip94FileMetadata/` | FileHeaderEvent.kt, FileStorageEvent.kt | File metadata (kind 1063) |
| 96 | `nip96FileStorage/` | HTTP file storage | HTTP-based file storage |
| 99 | `nip99Classifieds/` | ClassifiedsEvent.kt | Classifieds/marketplace (kind 30402) |
| A0 | `nipA0VoiceMessages/` | Voice messages | Voice message events |
| B7 | `nipB7Blossom/` | Blossom server URLs | Blossom file storage |
### Web/Storage/Other
| NIP | Directory | Key Files | Description |
|-----|-----------|-----------|-------------|
| 38 | `nip38UserStatus/` | StatusEvent.kt | User status (kind 30315) |
| 60 | `nip60Payment/` | Wallet events | Wallet info (kind 13194) |
| 61 | `nip61PaymentRequest/` | Nut zaps | Cashu payment requests |
| 64 | `nip64Chess/` | Chess moves | Chess move events |
| 66 | `nip66Monitoring/` | Relay monitor events | Relay monitoring |
| 67 | `nip67Invoices/` | Invoice tags | Lightning invoice tags |
| 69 | `nip69Offers/` | BOLT-12 offers | BOLT-12 offer tags |
| 70 | `nip70ProtectedEvts/` | Protected events | Protected event types |
| 73 | `nip73ExternalIds/` | External content IDs | External content identifiers |
| 78 | `nip78AppData/` | AppDataEvent.kt | Application data (kind 30078) |
| 79 | `nip79Labels/` | Label events | Labeling (kinds 1985, 1986) |
| 80-88 | Various | Various protocols | Relationship, preferences, polls, surveys, social graphs, etc. |
| 91 | `nip91Feed/` | Feed display events | Feed definitions |
| 93 | `nip93Gallery/` | Gallery events | Gallery collections |
| 95 | `nip95Storage/` | Storage event tags | Storage events |
| 97 | `nip97Nests/` | Audio rooms | Audio room events |
## Experimental NIPs (18 packages)
Located at `/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/`:
| Package | Description |
|---------|-------------|
| `audio/` | Audio content, track events |
| `bounties/` | Bounty/funding events |
| `decoupling/` | Decoupling setup |
| `edits/` | Event edit tracking |
| `ephemChat/` | Ephemeral encrypted chat |
| `forks/` | Fork tracking |
| `inlineMetadata/` | Inline metadata |
| `interactiveStories/` | Interactive story events |
| `limits/` | Limit enforcement |
| `medical/` | Medical data |
| `nip95/` | File storage support |
| `nipA3/` | A3 protocol extension |
| `nns/` | Nostr Name System |
| `profileGallery/` | Profile gallery lists |
| `publicMessages/` | Public message lists |
| `relationshipStatus/` | Relationship status events |
| `trustedAssertions/` | Trust/assertion events |
| `zapPolls/` | Zap-based polling |
## Quick Lookup by Kind
| Kind | Event Type | NIP |
|------|------------|-----|
| 0 | Metadata | 01 |
| 1 | Text Note | 01, 10 |
| 3 | Follow List | 02 |
| 4 | Encrypted DM (legacy) | 04 |
| 5 | Deletion | 09 |
| 6 | Repost | 18 |
| 7 | Reaction | 25 |
| 8 | Badge Award | 58 |
| 16 | Generic Repost | 18 |
| 40-44 | Channel Events | 28 |
| 1063 | File Metadata | 94 |
| 1111 | Comment | 22 |
| 1617, 1621, 1622, 1630, 1633 | Git | 34 |
| 1984 | Report | 56 |
| 1985, 1986 | Label | 79 |
| 9734 | Zap Request | 57 |
| 9735 | Zap Receipt | 57 |
| 9802 | Highlight | 84 |
| 10000-20000 | Replaceable Lists | 51 |
| 10002 | Relay List | 65 |
| 13194 | Wallet Info | 60 |
| 22242 | Relay Auth | 42 |
| 23194, 23195 | NWC Payment | 47 |
| 30000-40000 | Addressable Events | Various |
| 30009 | Badge Definition | 58 |
| 30023 | Long-Form Content | 23 |
| 30078 | App Data | 78 |
| 30311 | Live Event | 53 |
| 30315 | User Status | 38 |
| 30402 | Classifieds | 99 |
| 30818 | Wiki | 54 |
| 31234 | Draft | 37 |
| 31922-31925 | Calendar | 52 |
| 31989, 31990 | App Handlers | 89 |
| 34235, 34550-34551 | Video/Communities | 71, 72 |
| 5000-7000 | DVM Jobs | 90 |
## File Location Pattern
All NIPs located at: `/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip<NN><Name>/`
Example: NIP-57 → `/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip57Zaps/`
@@ -0,0 +1,336 @@
# Tag Patterns in Quartz
Tags are the primary way events reference other events, users, and metadata in Nostr.
## Tag Structure
```kotlin
typealias Tag = Array<String> // ["tag_name", "value", "optional_param", ...]
typealias TagArray = Array<Tag>
```
**Pattern**: `[name, value, ...optionalParams]`
## TagArrayBuilder DSL
```kotlin
fun tagArray(initializer: TagArrayBuilder<T>.() -> Unit): TagArray
```
**Methods**:
- `add(tag)` - Append tag
- `addFirst(tag)` - Prepend tag
- `addUnique(tag)` - Replace all tags with this name
- `remove(tagName)` - Remove all tags with name
- `removeIf(predicate, toCompare)` - Conditional removal
**Example**:
```kotlin
val tags = tagArray<TextNoteEvent> {
add(arrayOf("e", eventId, relayHint, "reply"))
add(arrayOf("p", pubkey))
addUnique(arrayOf("subject", "Hello"))
}
```
## Core Tag Types (NIP-01)
### e-tag (Event Reference)
```kotlin
// ["e", <event-id>, <relay-hint>, <marker>]
arrayOf("e", eventId, "wss://relay.damus.io", "reply")
```
**Markers** (NIP-10):
- `root` - Root of thread
- `reply` - Direct reply target
- `mention` - Mentioned event (not reply)
**Extensions**:
```kotlin
// nip01Core/tags/
fun TagArrayBuilder.eTag(eventId: HexKey, relay: String? = null, marker: String? = null)
```
### p-tag (Pubkey Reference)
```kotlin
// ["p", <pubkey>, <relay-hint>]
arrayOf("p", pubkey, "wss://relay.damus.io")
```
**Usage**: Tag users, indicate recipients
**Extensions**:
```kotlin
fun TagArrayBuilder.pTag(pubkey: HexKey, relay: String? = null)
```
### a-tag (Addressable Event Reference)
```kotlin
// ["a", <kind>:<pubkey>:<d-tag>, <relay-hint>]
arrayOf("a", "30023:${authorPubkey}:${dtag}", "wss://relay.damus.io")
```
**Usage**: Reference replaceable/addressable events (kinds 10000-20000, 30000-40000)
**Extensions**:
```kotlin
fun TagArrayBuilder.aTag(kind: Int, pubkey: HexKey, dTag: String, relay: String? = null)
```
### d-tag (Identifier)
```kotlin
// ["d", <identifier>]
arrayOf("d", "my-article-slug")
```
**Usage**: Unique identifier for addressable events
## Common Tag Extensions
### Subject (NIP-14)
```kotlin
// nip14Subject/
fun Event.subject(): String?
fun TagArrayBuilder.subject(text: String)
```
### Content Warning (NIP-36)
```kotlin
// nip36SensitiveContent/
fun Event.contentWarning(): String?
fun TagArrayBuilder.contentWarning(reason: String = "")
```
### Expiration (NIP-40)
```kotlin
// nip40Expiration/
fun Event.expiration(): Long?
fun TagArrayBuilder.expiration(unixTimestamp: Long)
```
### Alt Description (NIP-31)
```kotlin
// nip31Alts/
fun Event.alt(): String?
fun TagArrayBuilder.alt(description: String)
```
## Specialized Tags
### Zap Tags (NIP-57)
```kotlin
// nip57Zaps/tags/
class BoltTag(val bolt11: String, val preimage: String?)
class DescriptionTag(val zapRequestJson: String)
```
### Imeta Tags (NIP-92)
```kotlin
// nip92IMeta/
class IMetaTag(val url: String, val metadata: Map<String, String>)
// Usage: Image metadata
IMetaTag("https://example.com/image.jpg", mapOf(
"m" to "image/jpeg",
"dim" to "1920x1080",
"blurhash" to "..."
))
```
### Relay Tags (NIP-65)
```kotlin
// nip65RelayList/
class RelayTag(val url: String, val type: RelayType)
enum class RelayType { READ, WRITE, BOTH }
```
## Tag Query Patterns
### Finding Tags
```kotlin
// Extension functions on TagArray
fun TagArray.firstTag(name: String): Tag?
fun TagArray.allTags(name: String): List<Tag>
fun TagArray.tagValue(name: String): String?
fun TagArray.tagValues(name: String): List<String>
```
**Example**:
```kotlin
val event: TextNoteEvent = ...
val subject = event.tags.tagValue("subject")
val mentions = event.tags.allTags("p").mapNotNull { it.getOrNull(1) }
```
### Parsing Tags
```kotlin
// Pattern: Companion object with parse methods
object ETag {
fun parse(tag: Tag): ETag? {
if (tag.getOrNull(0) != "e") return null
return ETag(
eventId = tag.getOrNull(1) ?: return null,
relay = tag.getOrNull(2),
marker = tag.getOrNull(3)
)
}
}
// Usage
val eTags = event.tags.mapNotNull(ETag::parse)
```
## Event Builder Pattern
Combining TagArrayBuilder with event creation:
```kotlin
fun createTextNote(content: String, replyTo: Event?): EventTemplate {
return eventTemplate(
kind = 1,
content = content,
tags = tagArray {
replyTo?.let {
eTag(it.id, marker = "reply")
pTag(it.pubKey)
it.rootEvent()?.let { root ->
eTag(root.id, marker = "root")
}
}
}
)
}
```
## Hint System
Tags can provide "hints" - optional relay URLs for fetching referenced content:
```kotlin
// Event references
["e", eventId, "wss://relay.example.com"] // relay hint
// Pubkey references
["p", pubkey, "wss://relay.example.com"] // relay hint
// Addressable references
["a", "30023:pubkey:dtag", "wss://relay.example.com"] // relay hint
```
**Pattern**: Third parameter (index 2) is always the relay hint
## Tag Validation
```kotlin
// Common validations
fun validateETag(tag: Tag): Boolean {
return tag.getOrNull(0) == "e" && tag.getOrNull(1)?.isValidHex() == true
}
fun validatePTag(tag: Tag): Boolean {
return tag.getOrNull(0) == "p" && tag.getOrNull(1)?.isValidHex() == true
}
```
## Performance Patterns
### Tag Indexing
```kotlin
// TagArrayBuilder keeps an index by tag name
private val tagList = mutableMapOf<String, MutableList<Tag>>()
// Fast lookup by name
fun remove(tagName: String) {
tagList.remove(tagName)
}
```
### Lazy Parsing
```kotlin
// Don't parse all tags upfront
class TextNoteEvent(...) {
private val _mentions by lazy {
tags.mapNotNull(PTag::parse)
}
fun mentions() = _mentions
}
```
## Common Workflows
### Creating a Reply
```kotlin
fun replyTo(original: TextNoteEvent, content: String): EventTemplate {
return eventTemplate(
kind = 1,
content = content,
tags = tagArray {
// Reply to this event
eTag(original.id, marker = "reply")
// Copy root marker if exists, or mark original as root
original.rootEvent()?.let {
eTag(it.id, marker = "root")
} ?: eTag(original.id, marker = "root")
// Tag author
pTag(original.pubKey)
// Tag all mentioned users
original.mentions().forEach { pTag(it) }
}
)
}
```
### Creating a Reaction
```kotlin
fun createReaction(targetEvent: Event, emoji: String): EventTemplate {
return eventTemplate(
kind = 7,
content = emoji,
tags = tagArray {
eTag(targetEvent.id)
pTag(targetEvent.pubKey)
}
)
}
```
### Creating an Addressable Event
```kotlin
fun createArticle(title: String, content: String, slug: String): EventTemplate {
return eventTemplate(
kind = 30023,
content = content,
tags = tagArray {
addUnique(arrayOf("d", slug)) // Unique identifier
add(arrayOf("title", title))
add(arrayOf("published_at", "${TimeUtils.now()}"))
}
)
}
```
## Quick Reference
| Tag | NIP | Usage | Example |
|-----|-----|-------|---------|
| e | 01 | Event reference | `["e", eventId, relay, marker]` |
| p | 01 | Pubkey reference | `["p", pubkey, relay]` |
| a | 01 | Addressable event | `["a", "kind:pubkey:d"]` |
| d | 01 | Identifier | `["d", "unique-id"]` |
| subject | 14 | Subject line | `["subject", "Hello"]` |
| content-warning | 36 | Content warning | `["content-warning", "nsfw"]` |
| expiration | 40 | Expiration time | `["expiration", "1234567890"]` |
| bolt11 | 57 | Lightning invoice | `["bolt11", "lnbc..."]` |
| imeta | 92 | Media metadata | `["imeta", "url", "m", "image/jpeg"]` |
| relay | 65 | User relays | `["relay", "wss://...", "read"]` |
## Resources
- Tag builders: `quartz/src/commonMain/.../nip01Core/tags/`
- Tag extensions: Look for `TagArrayExt.kt`, `TagArrayBuilderExt.kt` in each NIP package
- Event parsing: Each event class has tag parsing methods