nmilat

module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: Unlicense

README

nmilat

nmilat is a Go SDK for building on the Nostr protocol. It handles the plumbing — event parsing, signing, verification, and 28+ NIPs — so you can focus on what you're building.

Use it to:

  • Build a client or bot that reads, signs, and publishes Nostr events
  • Run your own relay with the embeddable engine: event storage, sessions, and profile search included
  • Talk to remote relays over WebSocket without hand-rolling the wire protocol

No CLI or UI code lives here — this module is a library only. The ncli application (https://github.com/ohstr/ncli) is built on top of it.

Install

go get github.com/ohstr/nmilat

Package overview

Implemented NIPs
Package Purpose
nip01 Core event, filter, and subscription types (the foundation every other package builds on)
nip04, nip44, nip49 Encryption: direct messages, payloads, private keys
nip05 NIP-05 identity verification
nip09 Event deletion
nip11 Relay information document
nip13 Proof of work
nip16 Event treatment (regular/replaceable/ephemeral kinds)
nip17, nip59 Private direct messages, gift wraps
nip19 Bech32-encoded entities (npub, nsec, note, etc.)
nip23 Long-form content
nip26 Event delegation
nip33 Parameterized replaceable events
nip40 Event expiration
nip42, nip98 Relay/HTTP authentication
nip46 Nostr Connect (remote signing)
nip47 Wallet Connect (NWC): info/request/response/notification events, encryption negotiation, pairing URI
nip48 Proxy tags
nip57 Lightning zaps
nip65 Relay list metadata
nip77 Negentropy sync
nip88 Polls
nip90 Data Vending Machines
nipB0 Web bookmarks
nipB7 Blossom media (server list)
Relay engine and infrastructure
Package Purpose
relay Embeddable relay engine: event store, sessions, wire handlers, signature verification
relay/client Low-level relay client: WebSocket connect/subscribe/read, event-builder helpers
relay/migrations Embedded key-value store schema migrations used internally by relay
search Profile search indexing/ranking, used by relay for profile scoring
config Embedded YAML config for the search subsystem: profile scoring, NIP-05/LUD-16 verification bonuses, search-engine and cache tuning
wire Relay wire-protocol packet types (EVENT, REQ, EOSE, OK, NOTICE, AUTH)
utils Event/tag validation, NIP-05/LUD-16/nostrconnect helpers, key management, logging, and LNURL helpers shared across packages

Quick start

Run a relay
package main

import (
	"log"
	"net/http"

	"github.com/ohstr/nmilat/nip11"
	"github.com/ohstr/nmilat/relay"
)

func main() {
	metadata := &nip11.Metadata{
		Name:       "my-relay",
		Limitation: nip11.Limitation{MaxLimit: 1000, MaxMessageLength: 1024 * 1024},
	}

	store, err := relay.NewEventStore("relay.db", &metadata.Limitation)
	if err != nil {
		log.Fatal(err)
	}
	defer store.Close()

	handler := relay.NewSessionHandler(store, metadata, nil)
	log.Fatal(http.ListenAndServe(":8080", handler))
}

Connect with relayclient.NewConnection against ws://localhost:8080 (next example).

Read events from a relay

Connect, subscribe to a filter, and read events until EOSE. Relay input is untrusted, so always call Verify() before acting on an event:

package main

import (
	"context"
	"fmt"
	"net/url"

	"github.com/ohstr/nmilat/nip01"
	relayclient "github.com/ohstr/nmilat/relay/client"
)

func main() {
	filters := nip01.NewSubscriptionFilterGroup()
	filters.Add(&nip01.SubscriptionFilter{Kinds: []int{1}, Limit: 10})

	relayURL, _ := url.Parse("wss://relay.ohstr.com")
	events, err := relayclient.ReadEventsFromRelay(context.Background(), relayURL, filters)
	if err != nil {
		panic(err)
	}
	for _, ev := range events {
		if err := ev.Verify(); err != nil {
			continue // bad signature or ID — skip it
		}
		fmt.Println(ev.GetID(), ev.GetContent())
	}
}
Build, sign, and publish an event

Create an event, sign it with your private key, and publish it to a relay:

package main

import (
	"context"
	"fmt"
	"net/url"

	"github.com/ohstr/nmilat/nip01"
	relayclient "github.com/ohstr/nmilat/relay/client"
	"github.com/ohstr/nmilat/wire"
)

func main() {
	// pubkeyHex/privateKeyHex are your hex-encoded Nostr keypair — see
	// "Encode & decode keys" below for converting to/from npub/nsec.
	ev := nip01.NewEvent(1, pubkeyHex, "hello nostr")
	if err := ev.Sign(privateKeyHex); err != nil {
		panic(err)
	}

	relayURL, _ := url.Parse("wss://relay.ohstr.com")
	conn, err := relayclient.NewConnection(context.Background(), relayURL, nil)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	conn.Send(ev)

readLoop:
	for res := range conn.Read() {
		switch r := res.(type) {
		case *wire.OkSubscriptionResponse:
			fmt.Println("accepted:", r.Accepted, r.Message)
			break readLoop
		case *wire.NoticeSubscriptionResponse:
			fmt.Println("notice:", r.Message)
		default:
			fmt.Printf("unexpected response: %T\n", r)
		}
	}
}
Encode & decode keys (NIP-19)

Convert between raw hex keys and Nostr's bech32 encoding (npub/nsec):

import "github.com/ohstr/nmilat/nip19"

npub, err := nip19.EncodePublicKey(pubkeyHex)
if err != nil {
	panic(err)
}
fmt.Println(npub) // npub1...

prefix, value, err := nip19.Decode(npub)
if err != nil {
	panic(err)
}
fmt.Println(prefix, value) // npub <pubkeyHex>

Development

Uses just for build automation:

just build   # compile-check (library, no binary)
just test    # go test ./...
just vet     # go vet ./...
just tidy    # go mod tidy
just check   # build + vet + test

License

Unlicense — public domain.

Directories

Path Synopsis
pb
sha256
Package sha256 implements the SHA224 and SHA256 hash algorithms as defined in FIPS 180-4.
Package sha256 implements the SHA224 and SHA256 hash algorithms as defined in FIPS 180-4.
Package nip47 implements NIP-47: Nostr Wallet Connect (NWC), a protocol letting Nostr clients access a remote Lightning wallet service over end-to-end encrypted Nostr events, using a unique keypair per connection.
Package nip47 implements NIP-47: Nostr Wallet Connect (NWC), a protocol letting Nostr clients access a remote Lightning wallet service over end-to-end encrypted Nostr events, using a unique keypair per connection.
Package nip48 implements NIP-48: Proxy tags, which let an event declare that it originated on another protocol (ActivityPub, AT Protocol, RSS, or the web) and reference its source object there.
Package nip48 implements NIP-48: Proxy tags, which let an event declare that it originated on another protocol (ActivityPub, AT Protocol, RSS, or the web) and reference its source object there.
Package nip88 implements NIP-88: Polls, allowing clients to publish single- or multiple-choice polls (kind 1068) and votes on them (kind 1018).
Package nip88 implements NIP-88: Polls, allowing clients to publish single- or multiple-choice polls (kind 1068) and votes on them (kind 1018).
Package nip90 implements NIP-90: Data Vending Machines, a marketplace flow where customers announce compute jobs and service providers compete to fulfill them.
Package nip90 implements NIP-90: Data Vending Machines, a marketplace flow where customers announce compute jobs and service providers compete to fulfill them.
Package nipB0 implements NIP-B0: Web Bookmarking, editable bookmarks of web pages published as kind 39701 parameterized-replaceable events.
Package nipB0 implements NIP-B0: Web Bookmarking, editable bookmarks of web pages published as kind 39701 parameterized-replaceable events.
Package nipB7 implements NIP-B7: Blossom media, letting clients discover a user's preferred Blossom (BUD-01/BUD-03) file servers via a kind:10063 event, and locate a file by its SHA-256 hash across those servers when its original URL becomes unavailable.
Package nipB7 implements NIP-B7: Blossom media, letting clients discover a user's preferred Blossom (BUD-01/BUD-03) file servers via a kind:10063 event, and locate a file by its SHA-256 hash across those servers when its original URL becomes unavailable.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL