Add vitepress docs

This commit is contained in:
Ticruz
2025-02-04 14:43:40 +01:00
committed by Jon Staab
parent 43255bcb74
commit 94375a56ec
84 changed files with 10821 additions and 139 deletions
+110
View File
@@ -0,0 +1,110 @@
# Nostr Address
The Address module provides utilities for working with Nostr Addresses (NIP-19 naddr format) and handles the conversion between different address formats.
## Address Class
```typescript
class Address {
constructor(
readonly kind: number, // Event kind
readonly pubkey: string, // Author's public key
readonly identifier: string, // Unique identifier (d-tag)
readonly relays?: string[], // Optional relay hints
)
}
```
## Creating Addresses
### From Components
```typescript
const address = new Address(
30023, // kind (e.g., long-form article)
'ab82...123', // pubkey
'my-article-title', // identifier
['wss://relay.example.com'] // relays
)
```
### From String Format
```typescript
// Parse "kind:pubkey:identifier" format
const address = Address.from('30023:ab82...123:my-article-title')
// With optional relays
const address = Address.from(
'30023:ab82...123:my-article-title',
['wss://relay.example.com']
)
```
### From Naddr
```typescript
// Parse naddr format
const address = Address.fromNaddr('naddr1...')
```
### From Event
```typescript
const address = Address.fromEvent(event, relays)
```
## Converting Addresses
### To String
```typescript
const address = new Address(kind, pubkey, identifier)
address.toString() // => "kind:pubkey:identifier"
```
### To Naddr
```typescript
const address = new Address(kind, pubkey, identifier, relays)
address.toNaddr() // => "naddr1..."
```
## Utility Functions
### Check Address Format
```typescript
// Check if string is valid address format
Address.isAddress('30023:abc...123:title') // => true
Address.isAddress('not-an-address') // => false
```
### Get Address from Event
```typescript
import { getAddress } from '@welshman/util'
// Extract address from event
const address = getAddress(event)
```
## Examples
### Working with Long-form Content
```typescript
// Create address for article
const articleAddress = new Address(
30023, // Long-form content kind
authorPubkey,
'my-article-slug',
['wss://relay.example.com']
)
// Convert to string format for storage
const addressString = articleAddress.toString()
// Convert to naddr for sharing
const shareableAddress = articleAddress.toNaddr()
```
### Handling Replaceable Events
```typescript
// Create address from replaceable event
const address = Address.fromEvent(event)
// Store latest version using address as key
storage.set(address.toString(), event)
```
+162
View File
@@ -0,0 +1,162 @@
# Encryptable
The Encryptable module provides a system for handling encrypted Nostr events, particularly useful for private content like muted lists, bookmarks, or other encrypted user data.
## Core Types
### Encrypt Function
```typescript
type Encrypt = (x: string) => Promise<string>
```
### Encryptable Updates
```typescript
type EncryptableUpdates = {
content?: string
tags?: string[][]
}
```
### Decrypted Event
```typescript
type DecryptedEvent = TrustedEvent & {
plaintext: EncryptableUpdates
}
```
## Encryptable Class
```typescript
class Encryptable<T extends EventTemplate> {
constructor(
readonly event: Partial<T>, // Base event template
readonly updates: EncryptableUpdates // Plaintext updates
)
}
```
## Usage
### Basic Encryption
```typescript
// Create encryptable event
const encryptable = new Encryptable(
{ kind: 10000 }, // Base event
{ content: "secret content" } // Plaintext updates
)
// Encrypt and get final event
const event = await encryptable.reconcile(encryptFn)
```
### Private Lists
```typescript
// Create private mute list
const muteList = new Encryptable(
{
kind: 10000, // Mute list kind
tags: [] // Public tags
},
{
content: JSON.stringify(['pubkey1', 'pubkey2']), // Private content
tags: [['p', 'pubkey1'], ['p', 'pubkey2']] // Private tags
}
)
// Encrypt for publishing
const encrypted = await muteList.reconcile(async (content) => {
return await nip04.encrypt(pubkey, content)
})
```
### Updating Encrypted Content
```typescript
// Create encryptable from existing event
const existing = {
kind: 10000,
content: encryptedContent,
tags: publicTags
}
// Add new encrypted content
const updated = new Encryptable(
existing,
{
content: JSON.stringify(newContent),
tags: newPrivateTags
}
)
const final = await updated.reconcile(encrypt)
```
## Helper Functions
### Create Decrypted Event
```typescript
import { asDecryptedEvent } from '@welshman/util'
// Add plaintext content to event
const decrypted = asDecryptedEvent(
event,
{
content: decryptedContent,
tags: decryptedTags
}
)
```
## Examples
### Private Bookmarks
```typescript
// Create private bookmark list
const bookmarks = new Encryptable(
{
kind: 10003,
tags: [['d', 'bookmarks']] // Public identifier
},
{
content: JSON.stringify([
{ id: 'note1', title: 'Secret Note' }
])
}
)
// Encrypt for publishing
const event = await bookmarks.reconcile(async (content) => {
return await myEncryptionFunction(content)
})
```
### Encrypted Group Membership
```typescript
// Create private group member list
const members = new Encryptable(
{
kind: 30000,
tags: [['d', 'group-members']]
},
{
tags: members.map(m => ['p', m.pubkey, m.role])
}
)
const encrypted = await members.reconcile(encrypt)
```
### Updating Private Content
```typescript
function updatePrivateList(event: DecryptedEvent, newItems: string[]) {
return new Encryptable(
event,
{
content: JSON.stringify(newItems)
}
)
}
// Usage
const updated = updatePrivateList(existingEvent, newItems)
const final = await updated.reconcile(encrypt)
```
+185
View File
@@ -0,0 +1,185 @@
# Nostr Events
The Events module provides comprehensive type definitions and utilities for working with Nostr events, including helper functions for event creation, validation, and manipulation.
## Event Types Hierarchy
```typescript
// Base event with content and tags
interface EventContent {
tags: string[][]
content: string
}
// Base event with kind
interface EventTemplate extends EventContent {
kind: number
}
// Event with timestamp
interface StampedEvent extends EventTemplate {
created_at: number
}
// Event with author
interface OwnedEvent extends StampedEvent {
pubkey: string
}
// Event with ID
interface HashedEvent extends OwnedEvent {
id: string
}
// Event with signature
interface SignedEvent extends HashedEvent {
sig: string
[verifiedSymbol]?: boolean
}
// Event with wrapped content
interface UnwrappedEvent extends HashedEvent {
wrap: SignedEvent
}
// Event that can be either signed or wrapped
type TrustedEvent = HashedEvent & {
sig?: string
wrap?: SignedEvent
[verifiedSymbol]?: boolean
}
```
## Event Creation
### Create Basic Event
```typescript
import { createEvent } from '@welshman/util'
const event = createEvent(
1, // kind
{
content: "Hello Nostr!",
tags: [["t", "nostr"]],
created_at: now() // Optional, defaults to current time
}
)
```
## Type Guards
```typescript
// Check event types
isEventTemplate(event): boolean
isStampedEvent(event): boolean
isOwnedEvent(event): boolean
isHashedEvent(event): boolean
isSignedEvent(event): boolean
isUnwrappedEvent(event): boolean
isTrustedEvent(event): boolean
```
## Event Type Conversion
```typescript
// Convert to specific event types
asEventTemplate(event): EventTemplate
asStampedEvent(event): StampedEvent
asOwnedEvent(event): OwnedEvent
asHashedEvent(event): HashedEvent
asSignedEvent(event): SignedEvent
asUnwrappedEvent(event): UnwrappedEvent
asTrustedEvent(event): TrustedEvent
```
## Event Utilities
### Event Validation
```typescript
// Check if event has valid signature
hasValidSignature(event: SignedEvent): boolean
// Get event identifier (d tag)
getIdentifier(event: EventTemplate): string | undefined
```
### Event References
```typescript
// Get event ID or address
getIdOrAddress(event: HashedEvent): string
// Get both ID and address (if replaceable)
getIdAndAddress(event: HashedEvent): string[]
```
### Event Type Checking
```typescript
// Check event properties
isEphemeral(event: EventTemplate): boolean
isReplaceable(event: EventTemplate): boolean
isPlainReplaceable(event: EventTemplate): boolean
isParameterizedReplaceable(event: EventTemplate): boolean
```
### Thread & Reply Handling
```typescript
// Get thread information
getAncestors(event: EventTemplate): { roots: string[], replies: string[] }
// Get parent references
getParentIdsAndAddrs(event: EventTemplate): string[]
getParentIdOrAddr(event: EventTemplate): string | undefined
getParentId(event: EventTemplate): string | undefined
getParentAddr(event: EventTemplate): string | undefined
// Check reply relationship
isChildOf(child: EventTemplate, parent: HashedEvent): boolean
```
## Examples
### Creating and Processing Events
```typescript
// Create new event
const event = createEvent(1, {
content: "Hello world!",
tags: [["t", "greeting"]]
})
// Process based on type
if (isSignedEvent(event)) {
// Handle signed event
if (hasValidSignature(event)) {
processValidEvent(event)
}
} else if (isUnwrappedEvent(event)) {
// Handle wrapped event
processWrappedEvent(event)
}
```
### Working with Threads
```typescript
// Get thread context
const ancestors = getAncestors(event)
const rootId = ancestors.roots[0]
const replyTo = ancestors.replies[0]
// Check threading
if (isChildOf(event, parentEvent)) {
// Handle reply
}
```
### Type Conversion
```typescript
// Convert to needed type
const template = asEventTemplate(event)
const stamped = asStampedEvent(event)
const owned = asOwnedEvent(event)
const hashed = asHashedEvent(event)
const signed = asSignedEvent(event)
```
+165
View File
@@ -0,0 +1,165 @@
# Filters
The Filters module provides utilities for creating, manipulating, and matching Nostr event filters.
It includes support for filter operations, optimization, and time-based filtering.
## Core Types
```typescript
interface Filter {
ids?: string[] // Match specific event IDs
kinds?: number[] // Match event kinds
authors?: string[] // Match author pubkeys
since?: number // Match events since timestamp
until?: number // Match events until timestamp
limit?: number // Limit number of results
search?: string // Text search
[key: `#${string}`]: string[] // Tag filters
}
```
## Filter Operations
### Match Events
```typescript
// Match single filter
matchFilter(filter: Filter, event: HashedEvent): boolean
// Match multiple filters
matchFilters(filters: Filter[], event: HashedEvent): boolean
```
### Combine Filters
```typescript
// Combine filters with OR operation
unionFilters(filters: Filter[]): Filter[]
// Combine filters with AND operation
intersectFilters(groups: Filter[][]): Filter[]
```
### Filter Utilities
```typescript
// Get unique filter ID
getFilterId(filter: Filter): string
// Calculate filter group
calculateFilterGroup(filter: Filter): string
// Get filters for event IDs or addresses
getIdFilters(idsOrAddresses: string[]): Filter[]
// Get filters for reply events
getReplyFilters(events: TrustedEvent[], filter?: Filter): Filter[]
// Add repost filters
addRepostFilters(filters: Filter[]): Filter[]
```
## Time Constants
```typescript
// Unix epoch for Nostr (2021-01-01)
export const EPOCH = 1609459200
// One day in seconds
export const DAY = 86400
```
## Examples
### Basic Filtering
```typescript
// Create basic filter
const filter: Filter = {
kinds: [1], // Text notes
authors: ['pubkey1', 'pubkey2'],
since: now() - 24 * 60 * 60, // Last 24 hours
limit: 100
}
// Match event against filter
if (matchFilter(filter, event)) {
processEvent(event)
}
```
### Combining Filters
```typescript
// Union of filters (OR)
const combinedFilters = unionFilters([
{ kinds: [1], authors: ['pub1'] },
{ kinds: [1], authors: ['pub2'] }
])
// Intersection of filters (AND)
const intersectedFilters = intersectFilters([
[{ kinds: [1] }],
[{ authors: ['pub1'] }]
])
```
### Time-based Filtering
```typescript
// Filter events from specific time range
const timeFilter: Filter = {
since: now() - 7 * DAY, // Last week
until: now(),
limit: 100
}
// Guess appropriate time window
const delta = guessFilterDelta([timeFilter])
```
### Tag Filtering
```typescript
// Filter by tags
const tagFilter: Filter = {
'#t': ['nostr', 'bitcoin'], // Match hashtags
'#p': ['pubkey1'], // Match mentions
limit: 50
}
```
## Filter Optimization
### Trim Filters
```typescript
// Trim large filters to reasonable size
const trimmedFilter = trimFilter(filter)
const trimmedFilters = trimFilters(filters)
```
### Filter Analysis
```typescript
// Get filter generality score
const score = getFilterGenerality(filter)
// Get expected result count
const count = getFilterResultCardinality(filter)
```
## Advanced Usage
### Reply Chain Filters
```typescript
// Get filters for replies
const replyFilters = getReplyFilters(events, {
kinds: [1],
limit: 100
})
```
### Repost Handling
```typescript
// Add filters for reposts
const withReposts = addRepostFilters([
{ kinds: [1] } // Original filter
])
// Results in filters for kinds 1, 6, and 16
```
+133
View File
@@ -0,0 +1,133 @@
# Handlers (NIP-89)
The Handlers module provides functionality for working with handler recommendations and information (NIP-89).
Handlers are events that describe which kinds a given application can display.
This module provides utilities for transforming these events into structured handler objects that applications can easily process.
## Types
### Handler Definition
```typescript
type Handler = {
kind: number // Event kind this handler can process
name: string // Display name of the handler
about: string // Description
image: string // Icon or image URL
identifier: string // Unique identifier (d-tag)
event: TrustedEvent // Original handler event
website?: string // Optional website URL
lud16?: string // Optional Lightning address
nip05?: string // Optional NIP-05 identifier
}
```
## Core Functions
### Reading Handlers
```typescript
function readHandlers(event: TrustedEvent): Handler[]
// Example
const handlers = readHandlers(handlerEvent)
handlers.forEach(handler => {
console.log(`Handler for kind ${handler.kind}: ${handler.name}`)
})
```
### Handler Identification
```typescript
function getHandlerKey(handler: Handler): string
// Returns "kind:address" format
function getHandlerAddress(event: TrustedEvent): string | undefined
// Gets handler address from event tags
```
### Display Formatting
```typescript
function displayHandler(
handler?: Handler,
fallback = ""
): string
```
## Usage Examples
### Reading Handler Information
```typescript
const event = {
kind: 31990, // Handler Information kind
content: JSON.stringify({
name: "Note Viewer",
about: "Displays text notes with formatting",
image: "https://example.com/icon.png"
}),
tags: [
['k', '1'], // Handles kind 1 (text notes)
['d', 'note-viewer']
]
}
const handlers = readHandlers(event)
// Returns array of handlers defined in the event
```
### Working with Handlers
```typescript
// Get unique handler identifier
const key = getHandlerKey(handler)
// => "1:30023:note-viewer" (kind:pubkey:identifier)
// Display handler name
const name = displayHandler(handler, "Unknown Handler")
// => "Note Viewer" or fallback if handler undefined
// Get handler address
const address = getHandlerAddress(event)
// Returns address from tags with 'web' marker or first address
```
## Complete Example
```typescript
// Process handler information event
function processHandlerEvent(event: TrustedEvent) {
// Read all handlers from event
const handlers = readHandlers(event)
// Process each handler
handlers.forEach(handler => {
// Generate unique key
const key = getHandlerKey(handler)
// Store handler information
handlerRegistry.set(key, {
name: handler.name,
kind: handler.kind,
about: handler.about,
image: handler.image,
website: handler.website,
address: getHandlerAddress(handler.event)
})
})
}
// Find handler for event kind
function findHandler(kind: number): Handler | undefined {
return Array.from(handlerRegistry.values())
.find(h => h.kind === kind)
}
// Display handler information
function renderHandler(handler: Handler) {
return {
title: displayHandler(handler, "Unknown"),
description: handler.about,
icon: handler.image,
website: handler.website || null
}
}
```
+21
View File
@@ -0,0 +1,21 @@
# @welshman/util
A comprehensive utility package for Nostr application development, providing essential tools and types for working with Nostr events, addresses, profiles, and more.
## What's Included
- **Event Management**: Create, validate, and process Nostr events
- **Repository**: In-memory event storage with querying and indexing
- **Filters**: Advanced event filtering and subscription management
- **Profiles**: User profile handling and formatting
- **Lists**: Public and private list management
- **Zaps**: Lightning Network payment integration
- **Tags**: Comprehensive tag parsing and manipulation
- **Addresses**: NIP-19 address handling
- **Relays**: Relay URL handling, event dispatching and in-memory storage
## Installation
```
npm install @welshman/util
```
+71
View File
@@ -0,0 +1,71 @@
# Event Kinds
This module provides a comprehensive collection of Nostr event kind definitions and utilities.
It includes standard NIP event kinds as well as commonly used application-specific kinds.
## Kind Type Checkers
```typescript
// Check if kind is ephemeral (should not be stored)
export const isEphemeralKind = (kind: number): boolean
// Check if kind is replaceable (only latest event matters)
export const isReplaceableKind = (kind: number): boolean
// Check if kind is plain replaceable (no parameters)
export const isPlainReplaceableKind = (kind: number): boolean
// Check if kind is parameterized replaceable
export const isParameterizedReplaceableKind = (kind: number): boolean
```
## Usage Examples
### Checking Event Types
```typescript
import { isReplaceableKind, PROFILE, NOTE } from '@welshman/util'
// Profile events are replaceable
isReplaceableKind(PROFILE) // => true
// Notes are not replaceable
isReplaceableKind(NOTE) // => false
```
### Working with DVMs
```typescript
import {
DVM_REQUEST_TEXT_SUMMARY,
DVM_RESPONSE_TEXT_SUMMARY,
isDVMKind
} from '@welshman/util'
// Create DVM request
const request = {
kind: DVM_REQUEST_TEXT_SUMMARY,
content: "Text to summarize"
}
// Check for DVM events
isDVMKind(event.kind) // => true for kinds 5000-7000
```
### Handling Replaceable Events
```typescript
import {
isReplaceableKind,
PROFILE,
LONG_FORM
} from '@welshman/util'
function handleEvent(event) {
if (isReplaceableKind(event.kind)) {
// Only keep latest version
replaceExistingEvent(event)
} else {
// Keep all versions
storeNewEvent(event)
}
}
```
+26
View File
@@ -0,0 +1,26 @@
# Links
A small module for handling Nostr URI manipulation.
## Core Functions
### fromNostrURI
```typescript
function fromNostrURI(s: string): string
// Examples
fromNostrURI('nostr:npub1...') // => 'npub1...'
fromNostrURI('nostr://npub1...') // => 'npub1...'
fromNostrURI('note1...') // => 'note1...'
```
Removes the `nostr:` or `nostr://` protocol prefix from a Nostr URI.
### toNostrURI
```typescript
function toNostrURI(s: string): string
// Examples
toNostrURI('npub1...') // => 'nostr:npub1...'
toNostrURI('nostr:npub1...') // => 'nostr:npub1...' (unchanged)
```
Ensures a string has the `nostr:` protocol prefix.
+175
View File
@@ -0,0 +1,175 @@
# Lists
The Lists module provides utilities for working with Nostr lists, including both public and private lists (like bookmarks, mute lists, etc.). It handles list creation, encryption, and manipulation.
## Core Types
### List Parameters
```typescript
interface ListParams {
kind: number // List kind (e.g., 10000 for mutes)
}
```
### List Structure
```typescript
interface List extends ListParams {
publicTags: string[][] // Publicly visible tags
privateTags: string[][] // Encrypted tags
event?: DecryptedEvent // Original event if list exists
}
```
### Published List
```typescript
interface PublishedList extends List {
event: DecryptedEvent // Required event for published lists
}
```
## List Creation
### Create New List
```typescript
function makeList(list: ListParams & Partial<List>): List
// Example
const muteList = makeList({
kind: 10000,
publicTags: [['d', 'mutes']],
privateTags: [['p', 'pubkey1'], ['p', 'pubkey2']]
})
```
### Read Existing List
```typescript
function readList(event: DecryptedEvent): PublishedList
// Example
const list = readList(decryptedEvent)
```
## List Operations
### Get All Tags
```typescript
function getListTags(list: List | undefined): string[][]
// Example
const allTags = getListTags(list) // Combines public and private tags
```
### Remove Items
```typescript
// Remove by predicate
function removeFromListByPredicate(
list: List,
pred: (t: string[]) => boolean
): Encryptable
// Remove by value
function removeFromList(
list: List,
value: string
): Encryptable
```
### Add Items
```typescript
// Add public items
function addToListPublicly(
list: List,
...tags: string[][]
): Encryptable
// Add private items
function addToListPrivately(
list: List,
...tags: string[][]
): Encryptable
```
## Usage Examples
### Creating a Private List
```typescript
// Create new mute list
const muteList = makeList({
kind: 10000,
publicTags: [
['d', 'mutes'],
['name', 'My Mute List']
]
})
// Add items privately
const updated = addToListPrivately(
muteList,
['p', 'pubkey1'],
['p', 'pubkey2']
)
// Encrypt and publish
const encrypted = await updated.reconcile(encrypt)
```
### Reading and Updating Lists
```typescript
// Read existing list
const list = readList(decryptedEvent)
// Remove item
const removeItem = removeFromList(list, 'pubkey1')
// Add new items publicly
const addItems = addToListPublicly(
list,
['p', 'pubkey3'],
['p', 'pubkey4']
)
```
### Working with Tags
```typescript
// Get all list tags
const tags = getListTags(list)
// Remove by predicate
const noMentions = removeFromListByPredicate(
list,
tag => tag[0] === 'p'
)
```
## Common List Types
### Mute List
```typescript
const muteList = makeList({
kind: 10000,
publicTags: [['d', 'mutes']],
privateTags: [] // Keep muted users private
})
```
### Bookmark List
```typescript
const bookmarks = makeList({
kind: 10003,
privateTags: [
['e', 'id1'],
['e', 'id2']
]
})
```
### Relay List
```typescript
const relays = makeList({
kind: 10002,
publicTags: [[
['r', 'wss://relay1.com'],
['r', 'wss://relay2.com', 'write']
]
})
```
+115
View File
@@ -0,0 +1,115 @@
# Profile
The Profile module provides utilities for handling Nostr user profiles (kind 0 events), including profile creation, reading, and display formatting.
## Core Types
### Profile Structure
```typescript
interface Profile {
name?: string // Display name
nip05?: string // NIP-05 verification
lud06?: string // Legacy Lightning address
lud16?: string // Lightning address
lnurl?: string // Lightning URL
about?: string // Bio/description
banner?: string // Banner image URL
picture?: string // Profile picture URL
website?: string // Website URL
display_name?: string // Alternative display name
event?: TrustedEvent // Original profile event
}
```
### Published Profile
```typescript
interface PublishedProfile extends Omit<Profile, "event"> {
event: TrustedEvent // Required event for published profiles
}
```
## Core Functions
### Profile Creation & Reading
```typescript
// Create new profile
function makeProfile(profile: Partial<Profile>): Profile
// Read profile from event
function readProfile(event: TrustedEvent): PublishedProfile
// Create profile event
function createProfile(profile: Profile): EventTemplate
// Edit existing profile
function editProfile(profile: PublishedProfile): EventTemplate
```
### Display Formatting
```typescript
// Format pubkey for display
function displayPubkey(pubkey: string): string
// Format profile name for display
function displayProfile(
profile?: Profile,
fallback = ""
): string
// Check if profile has name
function profileHasName(profile?: Profile): boolean
```
## Usage Examples
### Creating New Profile
```typescript
// Create basic profile
const profile = makeProfile({
name: "Alice",
about: "Nostr user",
picture: "https://example.com/avatar.jpg",
lud16: "alice@getalby.com"
})
// Create profile event
const event = createProfile(profile)
```
### Reading Profile
```typescript
// Read profile from event
const profile = readProfile(profileEvent)
// Access profile data
console.log(profile.name)
console.log(profile.about)
console.log(profile.lnurl) // Auto-generated from lud16/lud06
```
### Displaying Profile
```typescript
// Display profile name
const name = displayProfile(profile, "Anonymous")
// Display pubkey
const shortPubkey = displayPubkey(profile.event.pubkey)
// => "npub1abc...xyz"
// Check for name
if (profileHasName(profile)) {
showName(profile)
} else {
showPubkey(profile)
}
```
### Updating Profile
```typescript
// Edit existing profile
const updated = editProfile({
...existingProfile,
name: "New Name",
about: "Updated bio"
})
```
+147
View File
@@ -0,0 +1,147 @@
# Relay
The `Relay` module provides utilities for working with Nostr relays, including a local in-memory relay implementation that integrates with [Repository](/util/repository) for event storage.
The Relay class extends EventEmitter to provide event-based communication.
## Core Components
### Relay Class
```typescript
class Relay<E extends HashedEvent = TrustedEvent> extends Emitter {
constructor(readonly repository: Repository<E>)
// Emit events: 'EVENT', 'EOSE', 'OK'
emit(type: string, ...args: any[]): boolean
// Handle relay messages
send(type: string, ...message: any[]): void
}
```
### Relay Profile
```typescript
interface RelayProfile {
url: string // Relay URL
name?: string // Display name
description?: string // Description
pubkey?: string // Operator's pubkey
contact?: string // Contact information
software?: string // Software name
version?: string // Software version
supported_nips?: number[] // Supported NIPs
limitation?: {
min_pow_difficulty?: number
payment_required?: boolean
auth_required?: boolean
}
}
```
### Finding Relay Information
```typescript
// Fetch relay information document
async function getRelayProfile(url: string): Promise<RelayProfile | null> {
try {
const normalized = normalizeRelayUrl(url)
// Convert ws/wss to http/https
const httpUrl = normalized.replace(/^ws(s)?:\/\//, 'http$1://')
// Fetch relay information document
const response = await fetch(`${httpUrl}`)
const info = await response.json()
return {
url: normalized,
name: info.name,
description: info.description,
pubkey: info.pubkey,
contact: info.contact,
software: info.software,
version: info.version,
supported_nips: info.supported_nips,
limitation: info.limitation
}
} catch (error) {
console.error(`Failed to fetch relay info for ${url}:`, error)
return null
}
}
```
## URL Utilities
### URL Validation
```typescript
// Check if URL is valid relay URL
isRelayUrl(url: string): boolean
// Check if URL is .onion address
isOnionUrl(url: string): boolean
// Check if URL is local
isLocalUrl(url: string): boolean
// Check if URL is IP address
isIPAddress(url: string): boolean
// Check if URL can be shared
isShareableRelayUrl(url: string): boolean
```
### URL Formatting
```typescript
// Normalize relay URL
normalizeRelayUrl(url: string): string
// Format URL for display
displayRelayUrl(url: string): string
// Format relay profile for display
displayRelayProfile(profile?: RelayProfile, fallback = ""): string
```
## Usage Examples
### URL Processing
```typescript
// Validate relay URL
if (isRelayUrl(url)) {
// Normalize for consistency
const normalized = normalizeRelayUrl(url)
// Check if shareable
if (isShareableRelayUrl(normalized)) {
// Format for display
const display = displayRelayUrl(normalized)
showRelay(display)
}
}
```
### Relay usage with Repository
```typescript
// Create storage and relay interface
const repository = new Repository()
const relay = new Relay(repository)
// Subscribe to events
relay.send("REQ", "sub_id", {
kinds: [1],
limit: 100
})
// Listen for events
relay.on("EVENT", (subId, event) => {
console.log(`Received event for ${subId}:`, event)
})
// Publish event
// Will be stored in repository and sent to matching subscribers
relay.send("EVENT", signedEvent)
// Close subscription
relay.send("CLOSE", "sub_id")
```
+115
View File
@@ -0,0 +1,115 @@
# Repository
The Repository module provides a robust in-memory event storage system with indexing, querying, and event replacement capabilities.
## Core Features
- Event storage and indexing
- Query support with multiple filters
- Event replacement and deletion tracking
- Event update notifications
- Optimized indexes for common queries
## Class Definition
```typescript
class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
// Storage indexes
eventsById = new Map<string, E>()
eventsByWrap = new Map<string, E>()
eventsByAddress = new Map<string, E>()
eventsByTag = new Map<string, E[]>()
eventsByDay = new Map<number, E[]>()
eventsByAuthor = new Map<string, E[]>()
eventsByKind = new Map<number, E[]>()
deletes = new Map<string, number>()
}
```
## Core Methods
### Event Management
```typescript
// Store or update event
publish(event: E, opts = { shouldNotify: true }): boolean
// Get event by ID or address
getEvent(idOrAddress: string): E | undefined
// Check if event exists
hasEvent(event: E): boolean
// Remove event
removeEvent(idOrAddress: string): void
// Check deletion status
isDeleted(event: E): boolean
isDeletedByAddress(event: E): boolean
isDeletedById(event: E): boolean
```
### Querying
```typescript
// Query events with filters
query(
filters: Filter[],
opts = {
includeDeleted: false,
shouldSort: true
}
): E[]
// Dump all events
dump(): E[]
// Load events in bulk
load(events: E[], chunkSize = 1000): void
```
## Usage Examples
### Basic Repository Operations
```typescript
// Create repository
const repo = new Repository<TrustedEvent>()
// Add events
repo.publish(event)
// Query events
const events = repo.query([
{ kinds: [1], limit: 100 }
])
// Check event status
if (!repo.isDeleted(event)) {
processEvent(event)
}
```
### Bulk Operations
```typescript
// Load multiple events
repo.load(events, 500) // Process in chunks of 500
// Get all events
const allEvents = repo.dump()
```
### Query Examples
```typescript
// Query with multiple filters
const events = repo.query([
// Recent events from specific authors
{
kinds: [1],
authors: ['pub1', 'pub2'],
since: now() - 24 * 60 * 60
},
// Events with specific tags
{
'#t': ['bitcoin', 'nostr'],
limit: 50
}
])
```
+149
View File
@@ -0,0 +1,149 @@
# Tags
The Tags module provides comprehensive utilities for working with Nostr event tags, including helpers for extracting, validating, and manipulating different types of tags.
## Core Functions
### Basic Tag Operations
```typescript
// Get tags by type(s)
getTags(types: string | string[], tags: string[][]): string[][]
// Get single tag by type(s)
getTag(types: string | string[], tags: string[][]): string[] | undefined
// Get tag values
getTagValues(types: string | string[], tags: string[][]): string[]
// Get single tag value
getTagValue(types: string | string[], tags: string[][]): string | undefined
```
## Tag Type Extractors
### Event References
```typescript
// Get 'e' tags (event references)
getEventTags(tags: string[][]): string[][]
getEventTagValues(tags: string[][]): string[]
// Get 'a' tags (event addresses)
getAddressTags(tags: string[][]): string[][]
getAddressTagValues(tags: string[][]): string[]
```
### Profile References
```typescript
// Get 'p' tags (pubkey references)
getPubkeyTags(tags: string[][]): string[][]
getPubkeyTagValues(tags: string[][]): string[]
```
### Topics and Relays
```typescript
// Get 't' tags (topics/hashtags)
getTopicTags(tags: string[][]): string[][]
getTopicTagValues(tags: string[][]): string[]
// Get 'r' and 'relay' tags
getRelayTags(tags: string[][]): string[][]
getRelayTagValues(tags: string[][]): string[]
```
### Groups and Kinds
```typescript
// Get group tags
getGroupTags(tags: string[][]): string[][]
getGroupTagValues(tags: string[][]): string[]
// Get 'k' tags (kind references)
getKindTags(tags: string[][]): string[][]
getKindTagValues(tags: string[][]): number[]
```
## Thread Management
### Comment Tags
```typescript
// Get root and reply references
getCommentTags(tags: string[][]): {
roots: string[][],
replies: string[][]
}
getCommentTagValues(tags: string[][]): {
roots: string[],
replies: string[]
}
```
### Reply Tags
```typescript
// Get detailed reply structure
getReplyTags(tags: string[][]): {
roots: string[][], // Thread roots
replies: string[][], // Direct replies
mentions: string[][] // Mentions
}
getReplyTagValues(tags: string[][]): {
roots: string[],
replies: string[],
mentions: string[]
}
```
## Utility Functions
```typescript
// Remove duplicate tags
uniqTags(tags: string[][]): string[][]
// Parse imeta tags into array of tag arrays
tagsFromIMeta(imeta: string[]): string[][]
```
## Usage Examples
### Basic Tag Handling
```typescript
// Get specific tag types
const pubkeys = getPubkeyTagValues(event.tags)
const topics = getTopicTagValues(event.tags)
const relays = getRelayTagValues(event.tags)
// Get multiple tag types
const refs = getTags(['p', 'e'], event.tags)
// Get single tag
const topic = getTagValue('t', event.tags)
```
### Thread Processing
```typescript
// Get thread context
const {roots, replies} = getReplyTags(event.tags)
// Process thread structure
function processThread(tags: string[][]) {
const thread = getReplyTags(tags)
return {
rootEvents: thread.roots.map(t => t[1]),
replyTo: thread.replies.map(t => t[1]),
mentions: thread.mentions.map(t => t[1])
}
}
```
### Tag Collection
```typescript
// Collect all references
function collectReferences(tags: string[][]) {
return {
events: getEventTagValues(tags),
profiles: getPubkeyTagValues(tags),
addresses: getAddressTagValues(tags)
}
}
```
+192
View File
@@ -0,0 +1,192 @@
# Zaps
The Zaps module provides utilities for working with Lightning Network payments (zaps) in Nostr, including LNURL handling, invoice amount parsing, and zap validation.
## Zapper Interface
The Zapper interface represents a Lightning Network payment provider that can process zaps:
```typescript
interface Zapper {
// LNURL for payment processing
lnurl: string
// User's pubkey on the payment service
pubkey?: string
// LNURL callback endpoint
callback?: string
// Minimum payment amount in millisatoshis
minSendable?: number
// Maximum payment amount in millisatoshis
maxSendable?: number
// Pubkey used to sign zap receipts
nostrPubkey?: string
// Whether provider supports Nostr zaps
allowsNostr?: boolean
}
```
### Finding Nostr Zappers
#### Getting Lightning Info
First, check the user's profile for Lightning addresses:
```typescript
function getLightningInfo(profile: Profile) {
// Check for Lightning Address (NIP-57)
if (profile.lud16) {
return {
type: 'lud16',
address: profile.lud16
}
}
// Check for LNURL
if (profile.lud06) {
return {
type: 'lud06',
url: profile.lud06
}
}
return null
}
```
#### Fetching LNURL Metadata
Once you have the Lightning address or LNURL, fetch the metadata:
```typescript
async function fetchZapper(address: string): Promise<Zapper | null> {
// Convert Lightning address to LNURL if needed
const lnurl = getLnUrl(address)
if (!lnurl) return null
try {
// Decode and fetch LNURL metadata
const url = new URL(bech32.decode(lnurl).data)
const response = await fetch(url.toString())
const metadata = await response.json()
// Extract zapper details
return {
lnurl,
callback: metadata.callback,
minSendable: metadata.minSendable,
maxSendable: metadata.maxSendable,
nostrPubkey: metadata.nostrPubkey,
allowsNostr: Boolean(metadata.allowsNostr),
}
} catch (error) {
console.error('Failed to fetch zapper:', error)
return null
}
}
```
```typescript
// Example Alby zapper configuration
const albyZapper: Zapper = {
lnurl: "lnurl1...",
pubkey: "alby_user_pubkey",
nostrPubkey: "alby_signing_key",
allowsNostr: true,
minSendable: 1000, // 1 sat minimum
maxSendable: 100000000 // 100k sats maximum
}
// Example LNbits zapper
const lnbitsZapper: Zapper = {
lnurl: "lnurl1...",
callback: "https://lnbits.com/callback",
nostrPubkey: "lnbits_signing_key",
allowsNostr: true
}
```
### Zap Structure
```typescript
interface Zap {
request: TrustedEvent // Zap request event kind 9734
response: TrustedEvent // Zap receipt/response event kind 9735 sent by the zapper
invoiceAmount: number // Amount in millisats
}
```
## Core Functions
### Lightning Address Handling
```typescript
// Convert address to LNURL
function getLnUrl(address: string): string | null
// Examples:
getLnUrl("user@domain.com") // => lnurl1...
getLnUrl("https://domain.com/.well-known/lnurlp/user") // => lnurl1...
getLnUrl("lnurl1...") // => returns unchanged
```
### Invoice Processing
```typescript
// Parse amount from BOLT11 invoice
function getInvoiceAmount(bolt11: string): number
// Convert human readable amount to millisats
function hrpToMillisat(hrpString: string): bigint
```
### Zap Validation
The `zapFromEvent` function validates a zap receipt event, against an expected zapper.
It returns a `Zap` object if the zap is valid, or `null` if not.
```typescript
function zapFromEvent(
response: TrustedEvent,
zapper: Zapper | undefined
): Zap | null
```
## Usage Examples
### Processing Lightning Addresses
```typescript
// Get LNURL from various formats
const lnurl1 = getLnUrl("user@getalby.com")
const lnurl2 = getLnUrl("https://getalby.com/.well-known/lnurlp/user")
const lnurl3 = getLnUrl("lnurl1...")
// Check if conversion was successful
if (lnurl1) {
// Process LNURL
processLnurl(lnurl1)
}
```
### Invoice Amount Handling
```typescript
// Get invoice amount in millisats
const amount = getInvoiceAmount(bolt11Invoice)
// Convert string amount to millisats
const millisats = hrpToMillisat("1000") // 1000 sats
const millisats = hrpToMillisat("1m") // 1 million sats
```
### Zap Validation
```typescript
// Validate zap event
const zap = zapFromEvent(zapResponse, albyZapper)
if (zap) {
// Process valid zap
processZap(zap)
}
```