nostr-expert

23
2
Source

Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details.

Install

mkdir -p .claude/skills/nostr-expert && curl -L -o skill.zip "https://mcp.directory/api/skills/download/1749" && unzip -o skill.zip -d .claude/skills/nostr-expert && rm skill.zip

Installs to .claude/skills/nostr-expert

About this skill

Nostr Protocol Expert (Quartz Implementation)

Practical patterns for working with Nostr in Quartz, AmethystMultiplatform's KMP Nostr library.

When to Use This Skill

  • Implementing Nostr event types (TextNote, Reaction, Zap, etc.)
  • Creating/parsing events with TagArrayBuilder DSL
  • Working with event kinds and tags
  • Finding NIP implementations in quartz/ codebase
  • Nostr cryptography (secp256k1 signing, NIP-44 encryption)
  • Bech32 encoding/decoding (npub, nsec, note formats)
  • Event validation and verification

For NIP specifications → Use nostr-protocol agent For Quartz implementation → Use this skill

Quartz Architecture

Quartz organizes code by NIP number:

quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/
├── nip01Core/           # Core protocol (Event, Kind, Tags)
├── nip04Dm/             # Legacy DMs (deprecated)
├── nip10Notes/          # Text notes with threading
├── nip17Dm/             # Private DMs (gift wrap)
├── nip19Bech32/         # Bech32 encoding
├── nip44Encryption/     # Modern encryption (ChaCha20)
├── nip57Zaps/           # Lightning zaps
├── ... (57 NIPs total)
└── experimental/        # Draft NIPs

Pattern: nip##<Name>/ directories contain event classes, tags, and utilities for that NIP.

Find implementations: Use scripts/nip-lookup.sh <nip-number> or see references/nip-catalog.md.

Event Anatomy

Core Structure

@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

Key insight: Event is the base class. Specific event types (TextNoteEvent, ReactionEvent) extend it and add parsing/helper methods.

Kind Classification

typealias Kind = Int

fun Kind.isEphemeral() = this in 20000..29999      // Not stored
fun Kind.isReplaceable() = this == 0 || this == 3 || this in 10000..19999
fun Kind.isAddressable() = this in 30000..39999    // Replaceable + has d-tag
fun Kind.isRegular() = this in 1000..9999          // Stored, not replaced

Pattern: Kind determines event lifecycle and replaceability.

Creating Events

EventTemplate Pattern

fun eventTemplate(
    kind: Kind,
    content: String,
    tags: TagArray = emptyArray()
): EventTemplate

Usage:

val template = eventTemplate(
    kind = 1,  // Text note
    content = "Hello Nostr!",
    tags = tagArray {
        add(arrayOf("subject", "Greeting"))
    }
)

// Sign with a signer
val signedEvent = signer.sign(template)

Why templates? Separates event data from signing. Templates can be signed by different signers (local keys, remote signers, hardware wallets).

TagArrayBuilder DSL

fun <T : Event> tagArray(
    initializer: TagArrayBuilder<T>.() -> Unit
): TagArray

Methods:

  • add(tag) - Append tag
  • addFirst(tag) - Prepend tag (for ordering)
  • addUnique(tag) - Replace all tags with this name
  • remove(tagName) - Remove by name
  • addAll(tags) - Bulk add

Example:

val tags = tagArray<TextNoteEvent> {
    add(arrayOf("e", replyToEventId, "", "reply"))
    add(arrayOf("p", authorPubkey))
    addUnique(arrayOf("subject", "Re: Hello"))
    add(arrayOf("content-warning", "spoilers"))
}

Pattern: Fluent DSL for building tag arrays with validation and deduplication.

Common Event Types

TextNoteEvent (kind 1)

class TextNoteEvent : BaseThreadedEvent

Creating:

val note = eventTemplate(
    kind = 1,
    content = "Hello world!",
    tags = tagArray {
        add(arrayOf("subject", "First post"))
    }
)

Parsing:

val event: TextNoteEvent = ...
val subject = event.subject()  // Extension from nip14Subject
val mentions = event.mentions()  // List of p-tags
val quotedEvents = event.quotes()  // List of q-tags

ReactionEvent (kind 7)

fun createReaction(
    targetEvent: Event,
    emoji: String = "+"
): EventTemplate {
    return eventTemplate(
        kind = 7,
        content = emoji,
        tags = tagArray {
            add(arrayOf("e", targetEvent.id))
            add(arrayOf("p", targetEvent.pubKey))
        }
    )
}

MetadataEvent (kind 0)

data class UserMetadata(
    val name: String?,
    val displayName: String?,
    val picture: String?,
    val banner: String?,
    val about: String?,
    // ... more fields
)

fun createMetadata(metadata: UserMetadata): EventTemplate {
    return eventTemplate(
        kind = 0,
        content = metadata.toJson()  // Serialize to JSON
    )
}

Addressable Events (kinds 30000-40000)

fun createArticle(
    slug: String,
    title: String,
    content: 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()}"))
        }
    )
}

Key: d-tag makes it addressable. Events with same kind + pubkey + d-tag replace each other.

Tag Patterns

Tags are Array<String> with pattern [name, value, ...optionalParams].

Core Tags

e-tag (event reference):

add(arrayOf("e", eventId, relayHint, marker))
// marker: "reply", "root", "mention"

p-tag (pubkey reference):

add(arrayOf("p", pubkey, relayHint))

a-tag (addressable event):

add(arrayOf("a", "$kind:$pubkey:$dtag", relayHint))

d-tag (identifier for addressable events):

addUnique(arrayOf("d", "unique-slug"))

Tag Extensions

// Find tags
event.tags.tagValue("subject")  // First subject tag value
event.tags.allTags("p")  // All p-tags
event.tags.tagValues("e")  // All e-tag values

// Parse structured tags
event.tags.mapNotNull(ETag::parse)  // Parse as ETag objects

For comprehensive tag patterns, see references/tag-patterns.md.

Threading (NIP-10)

fun createReply(
    original: TextNoteEvent,
    content: String
): EventTemplate {
    return eventTemplate(
        kind = 1,
        content = content,
        tags = tagArray {
            // Reply marker
            add(arrayOf("e", original.id, "", "reply"))

            // Root marker (original's root, or original itself)
            original.rootEvent()?.let {
                add(arrayOf("e", it.id, "", "root"))
            } ?: add(arrayOf("e", original.id, "", "root"))

            // Tag author
            add(arrayOf("p", original.pubKey))

            // Tag all mentioned users
            original.mentions().forEach {
                add(arrayOf("p", it))
            }
        }
    )
}

Pattern: reply and root markers establish thread hierarchy.

Cryptography

Signing (secp256k1)

interface ISigner {
    suspend fun sign(template: EventTemplate): Event
}

// Local key signing
class LocalSigner(private val privateKey: ByteArray) : ISigner {
    override suspend fun sign(template: EventTemplate): Event {
        val id = template.generateId()
        val sig = Secp256k1.sign(id, privateKey)
        return Event(id, pubKey, createdAt, kind, tags, content, sig)
    }
}

Pattern: Signers abstract key management. Can be local, remote (NIP-46), or hardware.

Encryption (NIP-44)

// Modern encryption (ChaCha20-Poly1305)
object Nip44v2 {
    fun encrypt(plaintext: String, privateKey: ByteArray, pubKey: HexKey): String
    fun decrypt(ciphertext: String, privateKey: ByteArray, pubKey: HexKey): String
}

// Usage
val encrypted = Nip44v2.encrypt(
    plaintext = "Secret message",
    privateKey = myPrivateKey,
    pubKey = recipientPubKey
)

val decrypted = Nip44v2.decrypt(
    ciphertext = encrypted,
    privateKey = myPrivateKey,
    pubKey = senderPubKey
)

Pattern: Elliptic curve Diffie-Hellman + ChaCha20-Poly1305 AEAD.

NIP-04 (Deprecated)

// Legacy encryption (NIP-04, deprecated for NIP-44)
object Nip04 {
    fun encrypt(msg: String, privateKey: ByteArray, pubKey: HexKey): String
    fun decrypt(msg: String, privateKey: ByteArray, pubKey: HexKey): String
}

Note: Use NIP-44 (Nip44v2) for new implementations. NIP-04 has security issues.

Bech32 Encoding (NIP-19)

object Nip19 {
    // Encode
    fun npubEncode(pubkey: HexKey): String  // npub1...
    fun nsecEncode(privateKey: ByteArray): String  // nsec1...
    fun noteEncode(eventId: HexKey): String  // note1...
    fun neventEncode(eventId: HexKey, relays: List<String> = emptyList()): String
    fun nprofileEncode(pubkey: HexKey, relays: List<String> = emptyList()): String
    fun naddrEncode(kind: Int, pubkey: HexKey, dTag: String, relays: List<String> = emptyList()): String

    // Decode
    fun decode(bech32: String): Nip19Result
}

sealed class Nip19Result {
    data class NPub(val hex: HexKey) : Nip19Result()
    data class NSec(val hex: HexKey) : Nip19Result()
    data class Note(val hex: HexKey) : Nip19Result()
    data class NEvent(val hex: HexKey, val relays: List<String>) : Nip19Result()
    data class NProfile(val hex: HexKey, val relays: List<String>) : Nip19Result()
    data class NAddr(val kind: Int, val pubkey: HexKey, val dTag: String, val relays: List<String>) : Nip19Result()
}

Usage:

// Encode
val npub = Nip19.npubEncode(pubkeyHex)
// Output: "npub1..."

// Decode
when (val result = Nip19.decode(npub)) {
    is Nip19Result.NPub -> println("Pubkey: ${result.hex}")
    is Nip19Result.NEvent -> println("Event: ${result.hex}, relays: ${result.relays}")
    else -> println("Other type

---

*Content truncated.*

kotlin-multiplatform

vitorpamplona

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

13123

kotlin-expert

vitorpamplona

Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.

224

compose-expert

vitorpamplona

Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).

304

kotlin-coroutines

vitorpamplona

Advanced Kotlin coroutines patterns for AmethystMultiplatform. Use when working with: (1) Structured concurrency (supervisorScope, coroutineScope), (2) Advanced Flow operators (flatMapLatest, combine, merge, shareIn, stateIn), (3) Channels and callbackFlow, (4) Dispatcher management and context switching, (5) Exception handling (CoroutineExceptionHandler, SupervisorJob), (6) Testing async code (runTest, Turbine), (7) Nostr relay connection pools and subscriptions, (8) Backpressure handling in event streams. Delegates to kotlin-expert for basic StateFlow/SharedFlow patterns. Complements nostr-expert for relay communication.

83

gradle-expert

vitorpamplona

Build optimization, dependency resolution, and multi-module KMP troubleshooting for AmethystMultiplatform. Use when working with: (1) Gradle build files (build.gradle.kts, settings.gradle), (2) Version catalog (libs.versions.toml), (3) Build errors and dependency conflicts, (4) Module dependencies and source sets, (5) Desktop packaging (DMG/MSI/DEB), (6) Build performance optimization, (7) Proguard/R8 configuration, (8) Common KMP + Android Gradle issues (Compose conflicts, secp256k1 JNI variants, source set problems).

71

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

1,5621,368

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

1,0991,183

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

1,4111,106

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

1,187745

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

1,144682

pdf-to-markdown

aliceisjustplaying

Convert entire PDF documents to clean, structured Markdown for full context loading. Use this skill when the user wants to extract ALL text from a PDF into context (not grep/search), when discussing or analyzing PDF content in full, when the user mentions "load the whole PDF", "bring the PDF into context", "read the entire PDF", or when partial extraction/grepping would miss important context. This is the preferred method for PDF text extraction over page-by-page or grep approaches.

1,295607

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.