Skip to content
Development
Skill

/quartz-integration

Integration guide for using the Quartz Nostr KMP library in external projects. Use when: (1) adding Quartz as a Gradle dependency, (2) setting up NostrClient with WebSocket, (3) creating/signing/sending events, (4) building relay subscriptions with Filter, (5) handling keys with

From plugin
amethyst
1.6k30 skills3 commands
Install
$ npx -y skills add vitorpamplona/amethyst --skill quartz-integration --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/quartz-integration

Context preview

The summary Claude sees to decide when to auto-load this skill.

Integration guide for using the Quartz Nostr KMP library in external projects. Use when: (1) adding Quartz as a Gradle dependency, (2) setting up NostrClient with WebSocket, (3) creating/signing/sending events, (4) building relay subscriptions with Filter, (5) handling keys with

SKILL.md

quartz-integration.SKILL.md
name: quartz-integration
description: Integration guide for using the Quartz Nostr KMP library in external projects. Use when: (1) adding Quartz as a Gradle dependency, (2) setting up NostrClient with WebSocket, (3) creating/signing/sending events, (4) building relay subscriptions with Filter, (5) handling keys with KeyPair/NostrSignerInternal, (6) using Bech32 encoding/decoding (NIP-19), (7) platform-specific setup (Android vs JVM/Desktop), (8) NIP-57 zaps, NIP-17 DMs, NIP-44 encryption in external projects, (9) running a relay on Quartz and serving/building its NIP-11 relay information document (application/nostr+json).

Quartz Integration Guide

Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr KMP projects.

**Published artifact**: `com.vitorpamplona.quartz:quartz:1.15.2` (Maven Central) **Targets**: JVM 21+, Android (minSdk 21+), iOS (XCFramework `quartz-kmpKit`) **License**: MIT

---

1. Gradle Setup

Version Catalog (`libs.versions.toml`)

[versions]
quartz = "1.15.2"

[libraries]
quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }

`build.gradle.kts` (KMP project)

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.quartz)
        }
    }
}

Android-only project

dependencies {
    implementation("com.vitorpamplona.quartz:quartz:1.15.2")
}

Transitive dependencies pulled in automatically

Quartz exposes these as `api` (you get them transitively):

| Dependency | Used for | |-----------|----------| | `fr.acinq.secp256k1:secp256k1-kmp-*` | Schnorr signing | | `com.github.anthonynsimon:rfc3986-normalizer` | Relay URL normalization | | `com.fasterxml.jackson.module:jackson-module-kotlin` | Event JSON parsing |

For Android, add to `build.gradle.kts`:

android {
    packaging {
        resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
    }
}

---

2. Key Concepts

Core Types

typealias HexKey = String        // 64-char hex string (pubkey, event id, sig)
typealias Kind = Int             // Event kind number
typealias TagArray = Array<Array<String>>

Event Anatomy

@Immutable
open class Event(
    val id: HexKey,        // SHA-256 of canonical JSON (64 hex chars)
    val pubKey: HexKey,    // Author public key (64 hex chars)
    val createdAt: Long,   // Unix timestamp (seconds)
    val kind: Kind,        // Event type
    val tags: TagArray,    // [["e","eventid"], ["p","pubkey"], ...]
    val content: String,
    val sig: HexKey,       // Schnorr signature (128 hex chars)
)

Kind Classification

// Regular events — stored by relays forever
val isRegular = kind in 1..9999

// Replaceable events — relay keeps only latest per (pubkey, kind)
val isReplaceable = kind == 0 || kind == 3 || kind in 10000..19999

// Addressable events — relay keeps latest per (pubkey, kind, d-tag)
val isAddressable = kind in 30000..39999

// Ephemeral events — relays don't persist
val isEphemeral = kind in 20000..29999

---

3. Key Management

Generate a new keypair

import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair

// Generate fresh random keys
val keyPair = KeyPair()

// From existing private key bytes
val keyPair = KeyPair(privKey = myPrivKeyBytes)

// Read-only (public key only, cannot sign)
val keyPair = KeyPair(pubKey = myPubKeyBytes)

// Access
val pubKeyHex: String = keyPair.pubKey.toHexKey()
val privKeyHex: String? = keyPair.privKey?.toHexKey()

Convert between formats

import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser

// ByteArray → hex
val hex = byteArray.toHexKey()

// hex → ByteArray
val bytes = hex.hexToByteArray()

// Bech32 import (npub, nsec)
val parsed = Nip19Parser.uriToRoute("npub1abc...")
// or
val parsed = Nip19Parser.uriToRoute("nsec1abc...")

> Hex ↔ ByteArray is a first-class utility in Quartz — see **§3.1 Hex utilities** below.

---

3.1 Hex utilities (HexKey ↔ ByteArray)

Nostr keys, event ids and signatures travel as lower-case hex strings. Quartz models this with the `HexKey` typealias (just a `String`) plus extension functions — **do not** write your own byte loop or pull in a third-party codec.

**Packages:** `com.vitorpamplona.quartz.nip01Core.core` (the extensions) and `com.vitorpamplona.quartz.utils` (the underlying `Hex` object).

import com.vitorpamplona.quartz.nip01Core.core.HexKey            // typealias = String
import com.vitorpamplona.quartz.nip01Core.core.toHexKey          // ByteArray → hex
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray    // hex → ByteArray
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArrayOrNull
import com.vitorpamplona.quartz.nip01Core.core.isValid
import com.vitorpamplona.quartz.utils.Hex

// Encode / decode
val hex: HexKey = pubKeyBytes.toHexKey()      // lower-case, 2 chars per byte
val bytes: ByteArray = hex.hexToByteArray()   // throws on odd length

// Untrusted input → decode safely
val maybe: ByteArray? = userInput.hexToByteArrayOrNull()  // null if not valid hex

// Validate without decoding (no allocation)
Hex.isHex(userInput)        // even-length, all hex digits (any length)
Hex.isHex64(userInput)      // fast path for a 32-byte key/id (checks first 64 chars)
hex.isValid()               // 64 chars AND valid hex (pubkey / event-id shape)

// Compare a hex string to raw bytes without decoding
Hex.isEqual(incomingHexId, myIdBytes)

| Need | Call | Notes | |------|------|-------| | ByteArray → hex | `bytes.toHexKey()` | lower-case output | | hex → ByteArray (strict) | `hex.hexToByteArray()` | throws on odd length | | hex → ByteArray (safe) | `hex.hexToByteArrayOrNull()` | `null` on invalid hex | | is this valid hex? | `Hex.isHex(s)` / `Hex.isHex64(s)` | `isHex64` ~30% faster for keys/ids | | i

Read more
Ships withamethyst

Nostr client for Android

Get the whole plugin
Stats
1,599
Stars
221
Forks
Active
Maintenance
Kotlin
Language
MIT
License
8h ago
Last commit
3y ago
Created

Repo: vitorpamplona/amethyst

Other skills on amethyst.