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.13.1` (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.13.1"

[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.13.1")
}

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

Other skills on amethyst.