nostr-effect
v0.0.12
Published
A type-safe, composable Nostr library built with Effect
Downloads
47
Maintainers
Readme
nostr-effect
A type-safe Nostr library built with Effect.
Why
We want the entire Nostr protocol—client library and relay—implemented in fully typed Effect TypeScript. This gives us composable error handling, dependency injection via layers, and structured concurrency out of the box.
Installation
bun add nostr-effect
# or
npm install nostr-effectWhat's Included
200+ exports covering the full Nostr protocol:
- See supported NIPs in
docs/SUPPORTED_NIPS.md(single source of truth) - Effect Services: CryptoService, EventService, DVMService, Nip05Service, Nip17Service, Nip25Service, Nip39Service, Nip46Service, Nip58Service, RelayService, and more
- Branded Types: NostrEvent, Filter, PublicKey, SecretKey, EventId, Signature, SubscriptionId, UnixTimestamp
- NIP-19 Encoding: encode/decode for npub, nsec, note, nprofile, nevent, naddr
- NIP-06 Keys: Seed word/mnemonic key derivation
- Relay Server: RelayServer, PolicyPipeline, NipRegistry, EventStore, SubscriptionManager
- Typed Errors: CryptoError, InvalidSignature, ValidationError, ConnectionError, and more
Quick Start
Promise API
The Promise API provides a simple interface inspired by nostr-tools. Under the hood, it uses the full Effect-based implementation with type-safe services and NIP modules.
Note: The Promise API covers the most common functionality. Full access to all NIPs and services is available via the Effect API.
import { generateSecretKey, getPublicKey, finalizeEvent, verifyEvent } from "nostr-effect/pure"
import { npubEncode, nsecEncode, decode } from "nostr-effect/nip19"
import { SimplePool } from "nostr-effect/pool"
// Generate keys
const sk = generateSecretKey()
const pk = getPublicKey(sk)
// Encode to bech32
const npub = npubEncode(pk)
const nsec = nsecEncode(sk)
// Create and sign an event
const event = finalizeEvent({
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: "Hello, Nostr!"
}, sk)
// Verify signature
console.log(verifyEvent(event)) // true
// Query relays
const pool = new SimplePool()
const events = await pool.querySync(
["wss://relay.damus.io", "wss://nos.lol"],
{ kinds: [1], limit: 10 }
)
pool.destroy()Effect API
import { Effect } from "effect"
import { CryptoService, EventService } from "nostr-effect"
import * as Nip19 from "nostr-effect"
const program = Effect.gen(function* () {
const crypto = yield* CryptoService
const events = yield* EventService
// Generate keypair
const keyPair = yield* crypto.generateKeyPair()
// Encode to npub
const npub = Nip19.npubEncode(keyPair.publicKey)
// Create and sign event
const event = yield* events.createSignedEvent({
kind: 1,
content: "Hello from Effect!",
tags: []
}, keyPair.secretKey)
return { npub, event }
})API Reference
Promise Wrappers
nostr-effect/pure
Key generation, event signing, and verification.
import {
generateSecretKey, // () => Uint8Array
getPublicKey, // (sk: Uint8Array) => string
finalizeEvent, // (template, sk) => VerifiedEvent
verifyEvent, // (event) => boolean
serializeEvent, // (event) => string
getEventHash, // (event) => string
validateEvent, // (event) => boolean
sortEvents, // (events) => void
} from "nostr-effect/pure"nostr-effect/nip19
Bech32 encoding/decoding for Nostr identifiers.
import {
npubEncode, // (pubkey: string) => NPub
nsecEncode, // (seckey: Uint8Array) => NSec
noteEncode, // (eventId: string) => Note
nprofileEncode, // ({ pubkey, relays? }) => NProfile
neventEncode, // ({ id, relays?, author?, kind? }) => NEvent
naddrEncode, // ({ identifier, pubkey, kind, relays? }) => NAddr
decode, // (bech32: string) => { type, data }
decodeNostrURI, // (uri: string) => { type, data }
NostrTypeGuard, // { isNPub, isNSec, isNote, ... }
} from "nostr-effect/nip19"nostr-effect/pool
SimplePool for managing relay connections.
import { SimplePool } from "nostr-effect/pool"
const pool = new SimplePool()
pool.subscribe(relays, filters, { onevent, oneose })
pool.subscribeIterator(relays, filters) // AsyncIterable
pool.querySync(relays, filters) // Promise<Event[]>
pool.get(relays, filters) // Promise<Event | null>
pool.publish(relays, event) // Promise<string>[]
pool.destroy()nostr-effect/relay
Single relay connection (simpler than SimplePool).
import { Relay, connectRelay } from "nostr-effect/relay"
// Create and connect
const relay = new Relay('wss://relay.damus.io')
relay.on('connect', () => console.log('Connected!'))
relay.on('error', (err) => console.error(err))
await relay.connect()
// Or use convenience function
const relay = await connectRelay('wss://relay.damus.io')
// Subscribe to events
const sub = relay.subscribe([{ kinds: [1], limit: 10 }], {
onevent: (event) => console.log(event),
oneose: () => console.log('End of stored events'),
})
// Publish an event
await relay.publish(signedEvent)
// Clean up
sub.close()
relay.close()nostr-effect/nip04
Legacy encrypted DMs (NIP-04).
import { encrypt, decrypt } from "nostr-effect/nip04"
// Encrypt a message (uses shared secret from sender's privkey + receiver's pubkey)
const ciphertext = await encrypt(senderSecretKey, receiverPubkey, "Hello!")
// Decrypt a message
const plaintext = await decrypt(receiverSecretKey, senderPubkey, ciphertext)nostr-effect/nip05
DNS-based identity verification (NIP-05).
import { queryProfile, isValid, searchDomain, NIP05_REGEX } from "nostr-effect/nip05"
// Look up a user's profile
const profile = await queryProfile('[email protected]')
if (profile) {
console.log('Pubkey:', profile.pubkey)
console.log('Relays:', profile.relays)
}
// Verify an identifier matches a pubkey
const valid = await isValid(pubkey, '[email protected]')
// Search for users on a domain
const users = await searchDomain('example.com', 'bob')nostr-effect/kinds
Event kind constants for all NIPs.
import { kinds } from "nostr-effect/kinds"
// Use kind constants
const event = { kind: kinds.ShortTextNote, ... } // kind 1
// Check if event is a reaction
if (event.kind === kinds.Reaction) { ... }
// Helper functions
kinds.isReplaceable(kind) // kind 0, 3, or 10000-19999
kinds.isEphemeral(kind) // kind 20000-29999
kinds.isParameterizedReplaceable(kind) // kind 30000-39999
kinds.getDVMResultKind(requestKind) // 5xxx -> 6xxxnostr-effect/utils
Helper utilities for working with events.
import {
matchFilter, matchFilters, // Check if event matches filter(s)
sortEvents, sortEventsAsc, // Sort events by timestamp
normalizeURL, // Normalize relay URLs
getTagValue, getTagValues, getTags, // Extract tags from events
deduplicateEvents, // Remove duplicate events by ID
getLatestReplaceable, // Get latest version of replaceable events
now, timestampToDate, dateToTimestamp, // Timestamp helpers
} from "nostr-effect/utils"
// Check if an event matches a filter
const matches = matchFilter({ kinds: [1], authors: [pubkey] }, event)
// Sort events newest first
const sorted = sortEvents(events)
// Get tag values
const referencedPubkeys = getTagValues(event, "p")Effect Services
nostr-effect (main)
import {
// Services
CryptoService, // Key generation, signing, encryption
EventService, // Event creation and validation
// Core types and schemas
NostrEvent, // Event schema
Filter, // Filter schema
PublicKey, // Branded type
SecretKey, // Branded type
EventId, // Branded type
Signature, // Branded type
// NIP modules (namespaced)
Nip04, // Legacy encrypted DMs
Nip06, // Key derivation from mnemonic
Nip11, // Relay information
Nip13, // Proof of Work
Nip17, // Private direct messages
Nip19, // bech32 encoding (Effect version)
Nip21, // nostr: URI scheme
Nip27, // Content parsing
Nip30, // Custom emoji
Nip34, // Git collaboration
Nip40, // Expiration timestamp
Nip42, // Client authentication
Nip47, // Nostr Wallet Connect
Nip49, // Encrypted private keys
Nip54, // Wiki
Nip59, // Gift wrap
Nip75, // Zap goals
Nip94, // File metadata
Nip98, // HTTP auth
Nip99, // Classified listings
} from "nostr-effect"nostr-effect/services
import {
CryptoService, // Key generation, schnorr signing
EventService, // Event creation and validation
Nip44Service, // NIP-44 encryption
} from "nostr-effect/services"nostr-effect/client
import {
Nip17Service, // Private direct messages
// ... other client services
} from "nostr-effect/client"nostr-effect/relay-server
import {
// Relay server implementation
RelayServer, PolicyPipeline, NipRegistry, EventStore, SubscriptionManager
} from "nostr-effect/relay-server"NIP Support
See the definitive support list: docs/SUPPORTED_NIPS.md.
License
CC0-1.0
