@toon-protocol/relay 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -2
- package/dist/index.js +9 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -152,8 +152,11 @@ declare class ConnectionHandler {
|
|
|
152
152
|
*/
|
|
153
153
|
private handleReq;
|
|
154
154
|
/**
|
|
155
|
-
* Handle an EVENT message
|
|
156
|
-
*
|
|
155
|
+
* Handle an EVENT message from a WebSocket client.
|
|
156
|
+
*
|
|
157
|
+
* Rejects all external writes — the relay is ILP-gated (pay to write).
|
|
158
|
+
* Events are only stored through the ILP packet handler which calls
|
|
159
|
+
* eventStore.store() directly and then broadcastEvent() to notify subscribers.
|
|
157
160
|
*/
|
|
158
161
|
private handleEvent;
|
|
159
162
|
/**
|
package/dist/index.js
CHANGED
|
@@ -505,25 +505,18 @@ var ConnectionHandler = class {
|
|
|
505
505
|
this.sendEose(subscriptionId);
|
|
506
506
|
}
|
|
507
507
|
/**
|
|
508
|
-
* Handle an EVENT message
|
|
509
|
-
*
|
|
508
|
+
* Handle an EVENT message from a WebSocket client.
|
|
509
|
+
*
|
|
510
|
+
* Rejects all external writes — the relay is ILP-gated (pay to write).
|
|
511
|
+
* Events are only stored through the ILP packet handler which calls
|
|
512
|
+
* eventStore.store() directly and then broadcastEvent() to notify subscribers.
|
|
510
513
|
*/
|
|
511
514
|
handleEvent(event) {
|
|
512
|
-
|
|
513
|
-
|
|
515
|
+
this.sendOk(
|
|
516
|
+
event.id,
|
|
517
|
+
false,
|
|
518
|
+
"restricted: writes require ILP payment"
|
|
514
519
|
);
|
|
515
|
-
try {
|
|
516
|
-
this.eventStore.store(event);
|
|
517
|
-
console.log(`[ConnectionHandler] Event stored successfully`);
|
|
518
|
-
this.sendOk(event.id, true, "");
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.error(`[ConnectionHandler] Failed to store event:`, error);
|
|
521
|
-
this.sendOk(
|
|
522
|
-
event.id,
|
|
523
|
-
false,
|
|
524
|
-
error instanceof Error ? error.message : "storage failed"
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
520
|
}
|
|
528
521
|
/**
|
|
529
522
|
* Handle a CLOSE message to terminate a subscription.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/filters/matchFilter.ts","../src/storage/InMemoryEventStore.ts","../src/storage/SqliteEventStore.ts","../src/toon/index.ts","../src/websocket/ConnectionHandler.ts","../src/websocket/NostrRelayServer.ts","../src/bls/types.ts","../src/bls/BusinessLogicServer.ts","../src/pricing/types.ts","../src/pricing/PricingService.ts","../src/pricing/config.ts","../src/subscriber/RelaySubscriber.ts","../src/index.ts"],"sourcesContent":["/**\n * Configuration options for the Nostr relay.\n */\nexport interface RelayConfig {\n /** Port to listen on (default: 7000) */\n port: number;\n /** Maximum concurrent connections (default: 100) */\n maxConnections?: number;\n /** Maximum subscriptions per connection (default: 20) */\n maxSubscriptionsPerConnection?: number;\n /** Maximum filters per subscription (default: 10) */\n maxFiltersPerSubscription?: number;\n /** Path to SQLite database file (default: ':memory:' for in-memory) */\n databasePath?: string;\n}\n\n/**\n * Default relay configuration values.\n */\nexport const DEFAULT_RELAY_CONFIG: Required<RelayConfig> = {\n port: 7000,\n maxConnections: 100,\n maxSubscriptionsPerConnection: 20,\n maxFiltersPerSubscription: 10,\n databasePath: ':memory:',\n};\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\n\n/**\n * Check if an event matches a single filter according to NIP-01 rules.\n *\n * Matching rules:\n * - All specified fields must match (AND logic)\n * - `ids` and `authors` support prefix matching\n * - Tag filters (#e, #p, etc.) match events with corresponding tags\n * - Empty filter matches all events\n *\n * @param event - The Nostr event to check\n * @param filter - The filter to match against\n * @returns true if the event matches the filter\n */\nexport function matchFilter(event: NostrEvent, filter: Filter): boolean {\n // Empty filter matches everything\n if (Object.keys(filter).length === 0) {\n return true;\n }\n\n // Check ids (prefix matching)\n if (filter.ids !== undefined && filter.ids.length > 0) {\n const matches = filter.ids.some((id) => event.id.startsWith(id));\n if (!matches) return false;\n }\n\n // Check authors (prefix matching)\n if (filter.authors !== undefined && filter.authors.length > 0) {\n const matches = filter.authors.some((author) =>\n event.pubkey.startsWith(author)\n );\n if (!matches) return false;\n }\n\n // Check kinds (exact matching)\n if (filter.kinds !== undefined && filter.kinds.length > 0) {\n if (!filter.kinds.includes(event.kind)) return false;\n }\n\n // Check since (created_at >= since)\n if (filter.since !== undefined) {\n if (event.created_at < filter.since) return false;\n }\n\n // Check until (created_at <= until)\n if (filter.until !== undefined) {\n if (event.created_at > filter.until) return false;\n }\n\n // Check tag filters (#e, #p, and generic #<single-letter>)\n for (const key of Object.keys(filter)) {\n if (key.startsWith('#') && key.length === 2) {\n const tagName = key.slice(1);\n const filterValues = filter[key as `#${string}`];\n\n if (filterValues !== undefined && filterValues.length > 0) {\n // Find matching tags in the event\n const eventTagValues = event.tags\n .filter((tag) => tag[0] === tagName)\n .map((tag) => tag[1]);\n\n // At least one filter value must match an event tag value\n const hasMatch = filterValues.some((v) => eventTagValues.includes(v));\n if (!hasMatch) return false;\n }\n }\n }\n\n return true;\n}\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\nimport { matchFilter } from '../filters/index.js';\n\n/**\n * Interface for event storage backends.\n */\nexport interface EventStore {\n /** Store an event by its ID */\n store(event: NostrEvent): void;\n /** Retrieve a single event by ID */\n get(id: string): NostrEvent | undefined;\n /** Query events matching any of the provided filters */\n query(filters: Filter[]): NostrEvent[];\n /** Close the storage backend (optional) */\n close?(): void;\n}\n\n/**\n * In-memory implementation of EventStore.\n * Events are stored in a Map keyed by event ID.\n */\nexport class InMemoryEventStore implements EventStore {\n private events = new Map<string, NostrEvent>();\n\n store(event: NostrEvent): void {\n this.events.set(event.id, event);\n }\n\n get(id: string): NostrEvent | undefined {\n return this.events.get(id);\n }\n\n query(filters: Filter[]): NostrEvent[] {\n // Get all events\n const allEvents = Array.from(this.events.values());\n\n // If no filters provided, return all events sorted by created_at desc\n if (filters.length === 0) {\n return allEvents.sort((a, b) => b.created_at - a.created_at);\n }\n\n // Find events matching ANY filter (OR logic between filters)\n const matchingEvents: NostrEvent[] = [];\n\n for (const event of allEvents) {\n for (const filter of filters) {\n if (matchFilter(event, filter)) {\n matchingEvents.push(event);\n break; // Only add once even if matches multiple filters\n }\n }\n }\n\n // Sort by created_at descending\n matchingEvents.sort((a, b) => b.created_at - a.created_at);\n\n // Apply limit from first filter that has one (NIP-01 semantics)\n const limitFilter = filters.find((f) => f.limit !== undefined);\n if (limitFilter?.limit !== undefined) {\n return matchingEvents.slice(0, limitFilter.limit);\n }\n\n return matchingEvents;\n }\n\n /**\n * Close the storage backend (no-op for in-memory store).\n */\n close(): void {\n // No-op for in-memory store\n }\n}\n","import Database from 'better-sqlite3';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\nimport type { EventStore } from './InMemoryEventStore.js';\n\n/**\n * SQL schema for the events table.\n */\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n pubkey TEXT NOT NULL,\n kind INTEGER NOT NULL,\n content TEXT NOT NULL,\n tags TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n sig TEXT NOT NULL,\n received_at INTEGER NOT NULL\n)\n`;\n\n/**\n * SQL for creating indexes on the events table.\n */\nconst INDEX_SQL = [\n 'CREATE INDEX IF NOT EXISTS idx_events_pubkey ON events(pubkey)',\n 'CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind)',\n 'CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_events_pubkey_kind ON events(pubkey, kind)',\n];\n\n/**\n * Initialize the database schema.\n */\nfunction initializeSchema(db: Database.Database): void {\n db.exec(SCHEMA_SQL);\n for (const indexSql of INDEX_SQL) {\n db.exec(indexSql);\n }\n}\n\n/**\n * Custom error class for relay storage errors.\n */\nexport class RelayError extends Error {\n constructor(\n message: string,\n public code: string\n ) {\n super(message);\n this.name = 'RelayError';\n }\n}\n\n/**\n * Check if an event kind is in the replaceable range (10000-19999).\n */\nfunction isReplaceableKind(kind: number): boolean {\n return kind >= 10000 && kind <= 19999;\n}\n\n/**\n * Check if an event kind is in the parameterized replaceable range (30000-39999).\n */\nfunction isParameterizedReplaceableKind(kind: number): boolean {\n return kind >= 30000 && kind <= 39999;\n}\n\n/**\n * Get the 'd' tag value from an event's tags array.\n */\nfunction getDTagValue(tags: string[][]): string {\n const dTag = tags.find((tag) => tag[0] === 'd');\n return dTag?.[1] ?? '';\n}\n\n/**\n * SQLite implementation of EventStore.\n * Persists events to a SQLite database file.\n */\nexport class SqliteEventStore implements EventStore {\n private db: Database.Database;\n private insertStmt: Database.Statement;\n private getStmt: Database.Statement;\n private deleteByPubkeyKindStmt: Database.Statement;\n private deleteByPubkeyKindDTagStmt: Database.Statement;\n private getByPubkeyKindStmt: Database.Statement;\n private getByPubkeyKindDTagStmt: Database.Statement;\n\n /**\n * Create a new SqliteEventStore.\n * @param dbPath - Path to the database file. Use ':memory:' for in-memory database.\n */\n constructor(dbPath = ':memory:') {\n try {\n this.db = new Database(dbPath);\n initializeSchema(this.db);\n\n // Prepare statements for better performance\n this.insertStmt = this.db.prepare(`\n INSERT OR REPLACE INTO events (id, pubkey, kind, content, tags, created_at, sig, received_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n this.getStmt = this.db.prepare('SELECT * FROM events WHERE id = ?');\n\n this.deleteByPubkeyKindStmt = this.db.prepare(\n 'DELETE FROM events WHERE pubkey = ? AND kind = ?'\n );\n\n this.deleteByPubkeyKindDTagStmt = this.db.prepare(\n \"DELETE FROM events WHERE pubkey = ? AND kind = ? AND json_extract(tags, '$') LIKE ?\"\n );\n\n this.getByPubkeyKindStmt = this.db.prepare(\n 'SELECT id, created_at FROM events WHERE pubkey = ? AND kind = ?'\n );\n\n this.getByPubkeyKindDTagStmt = this.db.prepare(\n 'SELECT id, created_at FROM events WHERE pubkey = ? AND kind = ? AND tags LIKE ?'\n );\n } catch (error) {\n throw new RelayError(\n `Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Store an event in the database.\n * Handles replaceable and parameterized replaceable events according to NIP-01.\n */\n store(event: NostrEvent): void {\n try {\n const tagsJson = JSON.stringify(event.tags);\n const receivedAt = Math.floor(Date.now() / 1000);\n\n if (isReplaceableKind(event.kind)) {\n // Replaceable event (10000-19999)\n this.storeReplaceableEvent(event, tagsJson, receivedAt);\n } else if (isParameterizedReplaceableKind(event.kind)) {\n // Parameterized replaceable event (30000-39999)\n this.storeParameterizedReplaceableEvent(event, tagsJson, receivedAt);\n } else {\n // Regular event - INSERT OR IGNORE to handle duplicates\n const insertOrIgnore = this.db.prepare(`\n INSERT OR IGNORE INTO events (id, pubkey, kind, content, tags, created_at, sig, received_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n insertOrIgnore.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n }\n } catch (error) {\n if (error instanceof RelayError) {\n throw error;\n }\n throw new RelayError(\n `Failed to store event: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Store a replaceable event (kinds 10000-19999).\n * Only keeps the latest event per pubkey+kind.\n */\n private storeReplaceableEvent(\n event: NostrEvent,\n tagsJson: string,\n receivedAt: number\n ): void {\n const existing = this.getByPubkeyKindStmt.get(event.pubkey, event.kind) as\n | { id: string; created_at: number }\n | undefined;\n\n if (existing) {\n // Only replace if new event is newer, or same time with lower id\n if (\n event.created_at > existing.created_at ||\n (event.created_at === existing.created_at && event.id < existing.id)\n ) {\n // Use transaction for atomicity\n const transaction = this.db.transaction(() => {\n this.deleteByPubkeyKindStmt.run(event.pubkey, event.kind);\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n });\n transaction();\n }\n // If existing event is newer or same, don't replace\n } else {\n // No existing event, just insert\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n }\n }\n\n /**\n * Store a parameterized replaceable event (kinds 30000-39999).\n * Only keeps the latest event per pubkey+kind+d-tag.\n */\n private storeParameterizedReplaceableEvent(\n event: NostrEvent,\n tagsJson: string,\n receivedAt: number\n ): void {\n const dTagValue = getDTagValue(event.tags);\n\n // For empty d-tag value, we need to match events that either:\n // 1. Have [\"d\", \"\"] in tags\n // 2. Have no d-tag at all (tags doesn't contain \"d\" as first element)\n let existing: { id: string; created_at: number } | undefined;\n\n if (dTagValue === '') {\n // Query for events with same pubkey and kind, then filter in code\n const candidates = this.db\n .prepare(\n 'SELECT id, created_at, tags FROM events WHERE pubkey = ? AND kind = ?'\n )\n .all(event.pubkey, event.kind) as {\n id: string;\n created_at: number;\n tags: string;\n }[];\n\n // Find one with empty or missing d-tag\n for (const candidate of candidates) {\n const candidateTags = JSON.parse(candidate.tags) as string[][];\n const candidateDTagValue = getDTagValue(candidateTags);\n if (candidateDTagValue === '') {\n existing = { id: candidate.id, created_at: candidate.created_at };\n break;\n }\n }\n } else {\n const dTagPattern = `%[\"d\",\"${dTagValue}\"%`;\n existing = this.getByPubkeyKindDTagStmt.get(\n event.pubkey,\n event.kind,\n dTagPattern\n ) as { id: string; created_at: number } | undefined;\n }\n\n if (existing) {\n // Only replace if new event is newer, or same time with lower id\n if (\n event.created_at > existing.created_at ||\n (event.created_at === existing.created_at && event.id < existing.id)\n ) {\n // Use transaction for atomicity - delete by ID for safety\n const transaction = this.db.transaction(() => {\n this.db.prepare('DELETE FROM events WHERE id = ?').run(existing.id);\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n });\n transaction();\n }\n // If existing event is newer or same, don't replace\n } else {\n // No existing event, just insert\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n }\n }\n\n /**\n * Retrieve an event by its ID.\n */\n get(id: string): NostrEvent | undefined {\n try {\n const row = this.getStmt.get(id) as\n | {\n id: string;\n pubkey: string;\n kind: number;\n content: string;\n tags: string;\n created_at: number;\n sig: string;\n }\n | undefined;\n\n if (!row) {\n return undefined;\n }\n\n return {\n id: row.id,\n pubkey: row.pubkey,\n kind: row.kind,\n content: row.content,\n tags: JSON.parse(row.tags) as string[][],\n created_at: row.created_at,\n sig: row.sig,\n };\n } catch (error) {\n throw new RelayError(\n `Failed to get event: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Query events matching any of the provided filters.\n */\n query(filters: Filter[]): NostrEvent[] {\n try {\n const { sql, params } = this.buildQuerySql(filters);\n const stmt = this.db.prepare(sql);\n const rows = stmt.all(...params) as {\n id: string;\n pubkey: string;\n kind: number;\n content: string;\n tags: string;\n created_at: number;\n sig: string;\n }[];\n\n return rows.map((row) => ({\n id: row.id,\n pubkey: row.pubkey,\n kind: row.kind,\n content: row.content,\n tags: JSON.parse(row.tags) as string[][],\n created_at: row.created_at,\n sig: row.sig,\n }));\n } catch (error) {\n throw new RelayError(\n `Failed to query events: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Build SQL query from filters.\n */\n private buildQuerySql(filters: Filter[]): { sql: string; params: unknown[] } {\n if (filters.length === 0) {\n return {\n sql: 'SELECT * FROM events ORDER BY created_at DESC',\n params: [],\n };\n }\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n for (const filter of filters) {\n const filterConditions: string[] = [];\n\n if (filter.ids?.length) {\n // Prefix matching with LIKE\n const idConditions = filter.ids.map(() => 'id LIKE ?');\n filterConditions.push(`(${idConditions.join(' OR ')})`);\n params.push(...filter.ids.map((id) => `${id}%`));\n }\n\n if (filter.authors?.length) {\n const authorConditions = filter.authors.map(() => 'pubkey LIKE ?');\n filterConditions.push(`(${authorConditions.join(' OR ')})`);\n params.push(...filter.authors.map((a) => `${a}%`));\n }\n\n if (filter.kinds?.length) {\n filterConditions.push(\n `kind IN (${filter.kinds.map(() => '?').join(', ')})`\n );\n params.push(...filter.kinds);\n }\n\n if (filter.since !== undefined) {\n filterConditions.push('created_at >= ?');\n params.push(filter.since);\n }\n\n if (filter.until !== undefined) {\n filterConditions.push('created_at <= ?');\n params.push(filter.until);\n }\n\n // Handle tag filters (#e, #p, etc.)\n for (const [key, values] of Object.entries(filter)) {\n if (key.startsWith('#') && Array.isArray(values) && values.length > 0) {\n const tagName = key.slice(1);\n const tagConditions = values.map(() => `tags LIKE ?`);\n filterConditions.push(`(${tagConditions.join(' OR ')})`);\n params.push(...values.map((v) => `%[\"${tagName}\",\"${v}\"%`));\n }\n }\n\n if (filterConditions.length > 0) {\n conditions.push(`(${filterConditions.join(' AND ')})`);\n }\n }\n\n let sql = 'SELECT * FROM events';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' OR ')}`;\n }\n sql += ' ORDER BY created_at DESC';\n\n // Apply limit from first filter that specifies it\n const limitFilter = filters.find((f) => f.limit !== undefined);\n if (limitFilter?.limit !== undefined) {\n sql += ' LIMIT ?';\n params.push(limitFilter.limit);\n }\n\n return { sql, params };\n }\n\n /**\n * Close the database connection.\n */\n close(): void {\n this.db.close();\n }\n}\n","export {\n encodeEventToToon,\n encodeEventToToonString,\n ToonEncodeError,\n} from '@toon-protocol/core';\nexport { decodeEventFromToon, ToonDecodeError } from '@toon-protocol/core';\n","import type { WebSocket } from 'ws';\nimport type { Filter } from 'nostr-tools/filter';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport type { RelayConfig } from '../types.js';\nimport { DEFAULT_RELAY_CONFIG } from '../types.js';\nimport { encodeEventToToonString } from '../toon/index.js';\nimport { matchFilter } from '../filters/index.js';\n\n/**\n * Represents an active subscription from a client.\n */\nexport interface Subscription {\n /** Unique subscription identifier from the client */\n id: string;\n /** Filters applied to this subscription */\n filters: Filter[];\n}\n\n/**\n * Handles NIP-01 messages for a single WebSocket connection.\n */\nexport class ConnectionHandler {\n private subscriptions = new Map<string, Subscription>();\n private config: Required<RelayConfig>;\n\n constructor(\n private ws: WebSocket,\n private eventStore: EventStore,\n config: Partial<RelayConfig> = {}\n ) {\n this.config = { ...DEFAULT_RELAY_CONFIG, ...config };\n }\n\n /**\n * Handle an incoming message from the WebSocket.\n */\n handleMessage(data: string): void {\n console.log(`[ConnectionHandler] Received message:`, data.slice(0, 150));\n let message: unknown[];\n\n try {\n const parsed = JSON.parse(data);\n if (!Array.isArray(parsed)) {\n this.sendNotice('error: invalid message format, expected JSON array');\n return;\n }\n message = parsed;\n } catch {\n this.sendNotice('error: invalid JSON');\n return;\n }\n\n const messageType = message[0];\n console.log(`[ConnectionHandler] Message type: ${messageType}`);\n\n if (messageType === 'REQ') {\n const subscriptionId = message[1];\n const filters = message.slice(2) as Filter[];\n this.handleReq(subscriptionId as string, filters);\n } else if (messageType === 'EVENT') {\n const event = message[1];\n this.handleEvent(event as NostrEvent);\n } else if (messageType === 'CLOSE') {\n const subscriptionId = message[1];\n this.handleClose(subscriptionId as string);\n } else {\n this.sendNotice(`error: unknown message type: ${messageType}`);\n }\n }\n\n /**\n * Handle a REQ message to create/update a subscription.\n */\n private handleReq(subscriptionId: string, filters: Filter[]): void {\n // Validate subscription ID\n if (typeof subscriptionId !== 'string' || subscriptionId.length === 0) {\n this.sendNotice('error: invalid subscription id');\n return;\n }\n\n // Check subscription limit (only for new subscriptions)\n if (!this.subscriptions.has(subscriptionId)) {\n if (\n this.subscriptions.size >= this.config.maxSubscriptionsPerConnection\n ) {\n this.sendNotice('error: too many subscriptions');\n return;\n }\n }\n\n // Check filter limit\n if (filters.length > this.config.maxFiltersPerSubscription) {\n this.sendNotice('error: too many filters');\n return;\n }\n\n // Store the subscription\n this.subscriptions.set(subscriptionId, {\n id: subscriptionId,\n filters,\n });\n\n // Query matching events\n console.log(\n `[ConnectionHandler] REQ: ${subscriptionId}, filters:`,\n JSON.stringify(filters).slice(0, 100)\n );\n const events = this.eventStore.query(filters);\n console.log(\n `[ConnectionHandler] Query returned ${events.length} events for ${subscriptionId}`\n );\n\n // Send matching events\n for (const event of events) {\n console.log(\n `[ConnectionHandler] Sending event ${event.id.slice(0, 16)}... to ${subscriptionId}`\n );\n this.sendEvent(subscriptionId, event);\n }\n\n // Send EOSE\n console.log(`[ConnectionHandler] Sending EOSE for ${subscriptionId}`);\n this.sendEose(subscriptionId);\n }\n\n /**\n * Handle an EVENT message to store an event.\n * Accepts events for free (no payment required).\n */\n private handleEvent(event: NostrEvent): void {\n console.log(\n `[ConnectionHandler] Received EVENT: ${event.id.slice(0, 16)}... kind:${event.kind}`\n );\n\n try {\n // Store the event\n this.eventStore.store(event);\n console.log(`[ConnectionHandler] Event stored successfully`);\n\n // Send OK message (NIP-20)\n this.sendOk(event.id, true, '');\n } catch (error) {\n console.error(`[ConnectionHandler] Failed to store event:`, error);\n // Send OK message with error (NIP-20)\n this.sendOk(\n event.id,\n false,\n error instanceof Error ? error.message : 'storage failed'\n );\n }\n }\n\n /**\n * Handle a CLOSE message to terminate a subscription.\n */\n private handleClose(subscriptionId: string): void {\n // Silently remove subscription (no error if it doesn't exist per NIP-01)\n this.subscriptions.delete(subscriptionId);\n }\n\n /**\n * Push a new event to all matching subscriptions on this connection.\n * Used when events are stored outside the WebSocket flow (e.g., via ILP).\n */\n notifyNewEvent(event: NostrEvent): void {\n for (const sub of this.subscriptions.values()) {\n const matches = sub.filters.some((f) => matchFilter(event, f));\n if (matches) {\n this.sendEvent(sub.id, event);\n }\n }\n }\n\n /**\n * Clean up all subscriptions for this connection.\n */\n cleanup(): void {\n this.subscriptions.clear();\n }\n\n /**\n * Get the number of active subscriptions.\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n private sendEvent(subscriptionId: string, event: NostrEvent): void {\n this.send(['EVENT', subscriptionId, encodeEventToToonString(event)]);\n }\n\n private sendEose(subscriptionId: string): void {\n this.send(['EOSE', subscriptionId]);\n }\n\n private sendOk(eventId: string, success: boolean, message: string): void {\n this.send(['OK', eventId, success, message]);\n }\n\n private sendNotice(message: string): void {\n this.send(['NOTICE', message]);\n }\n\n private send(message: unknown[]): void {\n if (this.ws.readyState === 1) {\n // OPEN\n this.ws.send(JSON.stringify(message));\n }\n }\n}\n","import type { WebSocket } from 'ws';\nimport { WebSocketServer } from 'ws';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport type { RelayConfig } from '../types.js';\nimport { DEFAULT_RELAY_CONFIG } from '../types.js';\nimport { ConnectionHandler } from './ConnectionHandler.js';\n\n/**\n * A NIP-01 compliant Nostr relay WebSocket server.\n * Handles client connections and routes messages to ConnectionHandlers.\n */\nexport class NostrRelayServer {\n private wss: WebSocketServer | null = null;\n private handlers = new Map<WebSocket, ConnectionHandler>();\n private config: Required<RelayConfig>;\n\n constructor(\n config: Partial<RelayConfig> = {},\n private eventStore: EventStore\n ) {\n this.config = { ...DEFAULT_RELAY_CONFIG, ...config };\n }\n\n /**\n * Start the WebSocket server.\n */\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.wss = new WebSocketServer({ port: this.config.port });\n\n this.wss.on('connection', (ws: WebSocket) => {\n this.handleConnection(ws);\n });\n\n this.wss.on('error', (error: Error) => {\n console.error('[NostrRelayServer] Server error:', error.message);\n });\n\n this.wss.on('listening', () => {\n const address = this.wss?.address();\n if (address && typeof address === 'object') {\n console.log(`[NostrRelayServer] Listening on port ${address.port}`);\n }\n resolve();\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Stop the WebSocket server and close all connections.\n */\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (!this.wss) {\n resolve();\n return;\n }\n\n // Clean up all connection handlers\n for (const [ws, handler] of this.handlers) {\n handler.cleanup();\n ws.close();\n }\n this.handlers.clear();\n\n this.wss.close(() => {\n this.wss = null;\n resolve();\n });\n });\n }\n\n /**\n * Get the port the server is listening on.\n * Returns 0 if the server is not started.\n */\n getPort(): number {\n if (!this.wss) return 0;\n const address = this.wss.address();\n if (address && typeof address === 'object') {\n return address.port;\n }\n return 0;\n }\n\n /**\n * Get the number of connected clients.\n */\n getClientCount(): number {\n return this.handlers.size;\n }\n\n /**\n * Broadcast an event to all connected clients with matching subscriptions.\n * Call this after storing an event outside the WebSocket flow (e.g., via ILP)\n * so that discovery subscribers are notified.\n */\n broadcastEvent(event: NostrEvent): void {\n for (const handler of this.handlers.values()) {\n handler.notifyNewEvent(event);\n }\n }\n\n private handleConnection(ws: WebSocket): void {\n // Check max connections\n if (this.handlers.size >= this.config.maxConnections) {\n ws.close(1013, 'max connections reached');\n return;\n }\n\n console.log('[NostrRelayServer] Client connected');\n\n const handler = new ConnectionHandler(ws, this.eventStore, this.config);\n this.handlers.set(ws, handler);\n\n ws.on('message', (data: Buffer | string) => {\n const message = typeof data === 'string' ? data : data.toString();\n handler.handleMessage(message);\n });\n\n ws.on('close', () => {\n console.log('[NostrRelayServer] Client disconnected');\n handler.cleanup();\n this.handlers.delete(ws);\n });\n\n ws.on('error', (error: Error) => {\n console.error('[NostrRelayServer] Client error:', error.message);\n handler.cleanup();\n this.handlers.delete(ws);\n });\n }\n}\n","import { RelayError } from '../storage/index.js';\nimport type { PricingService } from '../pricing/index.js';\n\n/**\n * Regex for validating Nostr pubkeys (64 lowercase hex characters).\n */\nexport const PUBKEY_REGEX = /^[0-9a-f]{64}$/;\n\n/**\n * Validate that a string is a valid Nostr pubkey format.\n * @param pubkey - The pubkey to validate\n * @returns true if valid 64-character lowercase hex string\n */\nexport function isValidPubkey(pubkey: string): boolean {\n return PUBKEY_REGEX.test(pubkey);\n}\n\n/**\n * Configuration for the Business Logic Server.\n */\nexport interface BlsConfig {\n /** Base price per byte for event storage (used for simple pricing) */\n basePricePerByte: bigint;\n /** Optional PricingService for kind-based pricing overrides */\n pricingService?: PricingService;\n /** Optional owner pubkey - events from this pubkey bypass payment */\n ownerPubkey?: string;\n}\n\n/**\n * Incoming packet request from ILP connector.\n */\nexport interface HandlePacketRequest {\n /** Payment amount as string (parsed to bigint) */\n amount: string;\n /** ILP destination address */\n destination: string;\n /** Base64-encoded TOON Nostr event */\n data: string;\n /** Source ILP address */\n sourceAccount?: string;\n}\n\n/**\n * Response for accepted packet.\n */\nexport interface HandlePacketAcceptResponse {\n accept: true;\n /** @deprecated Connector computes fulfillment from SHA256(toon_bytes). Will be removed in a future version. */\n fulfillment?: string;\n metadata?: {\n eventId: string;\n storedAt: number;\n };\n}\n\n/**\n * Response for rejected packet.\n */\nexport interface HandlePacketRejectResponse {\n accept: false;\n /** ILP error code (F00, F06, etc.) */\n code: string;\n /** Human-readable error message */\n message: string;\n metadata?: {\n required?: string;\n received?: string;\n };\n}\n\n/**\n * Union type for packet response.\n */\nexport type HandlePacketResponse =\n | HandlePacketAcceptResponse\n | HandlePacketRejectResponse;\n\n/**\n * ILP error code constants.\n */\nexport const ILP_ERROR_CODES = {\n BAD_REQUEST: 'F00',\n INSUFFICIENT_AMOUNT: 'F06',\n INTERNAL_ERROR: 'T00',\n} as const;\n\n/**\n * BLS-specific error class.\n */\nexport class BlsError extends RelayError {\n constructor(message: string, code = 'BLS_ERROR') {\n super(message, code);\n this.name = 'BlsError';\n }\n}\n","import { createHash } from 'crypto';\nimport { Hono } from 'hono';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport { decodeEventFromToon } from '../toon/index.js';\nimport type {\n BlsConfig,\n HandlePacketRequest,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n} from './types.js';\nimport { ILP_ERROR_CODES, BlsError, isValidPubkey } from './types.js';\n\n/**\n * Generate a fulfillment from an event ID.\n * The fulfillment is SHA-256(eventId) encoded as base64.\n *\n * Note: The sender must use SHA256(SHA256(eventId)) as the condition\n * in their ILP Prepare packet.\n *\n * @deprecated Connector computes fulfillment from SHA256(toon_bytes). BLS should not generate fulfillment.\n */\nexport function generateFulfillment(eventId: string): string {\n const hash = createHash('sha256').update(eventId).digest();\n return hash.toString('base64');\n}\n\n/**\n * Business Logic Server for ILP payment verification.\n *\n * Handles payment requests from an ILP connector, verifying that the\n * payment amount meets the required price for storing the included\n * Nostr event.\n */\nexport class BusinessLogicServer {\n private app: Hono;\n\n constructor(\n private config: BlsConfig,\n private eventStore: EventStore\n ) {\n // Validate ownerPubkey format if provided\n if (\n config.ownerPubkey !== undefined &&\n !isValidPubkey(config.ownerPubkey)\n ) {\n throw new BlsError(\n 'Invalid ownerPubkey format: must be 64 lowercase hex characters',\n 'INVALID_CONFIG'\n );\n }\n this.app = new Hono();\n this.setupRoutes();\n }\n\n /**\n * Set up HTTP routes.\n */\n private setupRoutes(): void {\n this.app.post('/handle-packet', async (c) => {\n try {\n const body = await c.req.json<HandlePacketRequest>();\n const response = this.handlePacket(body);\n return c.json(response, response.accept ? 200 : 400);\n } catch (error) {\n const response: HandlePacketRejectResponse = {\n accept: false,\n code: ILP_ERROR_CODES.INTERNAL_ERROR,\n message:\n error instanceof Error ? error.message : 'Internal server error',\n };\n return c.json(response, 500);\n }\n });\n\n this.app.get('/health', (c) => {\n return c.json({\n status: 'healthy',\n timestamp: Date.now(),\n });\n });\n }\n\n /**\n * Process a packet request.\n *\n * This method is public to support direct connector integration in embedded mode,\n * where the connector calls this method directly via setPacketHandler() instead\n * of making HTTP requests.\n */\n public handlePacket(\n request: HandlePacketRequest\n ): HandlePacketAcceptResponse | HandlePacketRejectResponse {\n // Validate required fields\n if (!request.amount || !request.destination || !request.data) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Missing required fields: amount, destination, data',\n };\n }\n\n // Decode base64 data\n let toonBytes: Uint8Array;\n try {\n toonBytes = Uint8Array.from(Buffer.from(request.data, 'base64'));\n } catch {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid base64 encoding in data field',\n };\n }\n\n // Decode TOON to Nostr event\n let event;\n try {\n event = decodeEventFromToon(toonBytes);\n } catch (error) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: `Invalid TOON data: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n\n // Verify event signature\n if (!verifyEvent(event)) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid event signature',\n };\n }\n\n // Self-write bypass: owner events skip payment verification\n if (this.config.ownerPubkey && event.pubkey === this.config.ownerPubkey) {\n try {\n this.eventStore.store(event);\n } catch (error) {\n throw new BlsError(\n `Failed to store event: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'STORAGE_ERROR'\n );\n }\n\n return {\n accept: true,\n fulfillment: generateFulfillment(event.id),\n metadata: {\n eventId: event.id,\n storedAt: Date.now(),\n },\n };\n }\n\n // Calculate price: use PricingService if provided, otherwise simple calculation\n const price = this.config.pricingService\n ? this.config.pricingService.calculatePriceFromBytes(\n toonBytes,\n event.kind\n )\n : BigInt(toonBytes.length) * this.config.basePricePerByte;\n\n // Parse and compare amounts\n let amount: bigint;\n try {\n amount = BigInt(request.amount);\n } catch {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid amount format',\n };\n }\n\n if (amount < price) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.INSUFFICIENT_AMOUNT,\n message: 'Insufficient payment amount',\n metadata: {\n required: price.toString(),\n received: amount.toString(),\n },\n };\n }\n\n // Store event\n try {\n this.eventStore.store(event);\n } catch (error) {\n throw new BlsError(\n `Failed to store event: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'STORAGE_ERROR'\n );\n }\n\n // Generate fulfillment and return success\n const storedAt = Date.now();\n return {\n accept: true,\n fulfillment: generateFulfillment(event.id),\n metadata: {\n eventId: event.id,\n storedAt,\n },\n };\n }\n\n /**\n * Get the Hono app instance for testing or composition.\n */\n getApp(): Hono {\n return this.app;\n }\n\n /**\n * Start the HTTP server on the specified port.\n */\n start(port: number): void {\n // Note: In Node.js, you would typically use @hono/node-server\n // For now, this method is a placeholder for actual server startup\n // The actual server binding would be done by the consumer\n console.log(`BLS would start on port ${port}`);\n }\n}\n","import { RelayError } from '../storage/index.js';\n\n/**\n * Configuration for the Pricing Service.\n */\nexport interface PricingConfig {\n /** Base price per byte for event storage */\n basePricePerByte: bigint;\n /** Optional price overrides by event kind */\n kindOverrides?: Map<number, bigint>;\n}\n\n/**\n * Error class for pricing-specific errors.\n */\nexport class PricingError extends RelayError {\n constructor(message: string, code = 'PRICING_ERROR') {\n super(message, code);\n this.name = 'PricingError';\n }\n}\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport { encodeEventToToon } from '../toon/index.js';\nimport type { PricingConfig } from './types.js';\nimport { PricingError } from './types.js';\n\n/**\n * Service for calculating event storage prices with kind-based overrides.\n */\nexport class PricingService {\n private readonly basePricePerByte: bigint;\n private readonly kindOverrides: Map<number, bigint>;\n\n constructor(config: PricingConfig) {\n // Validate basePricePerByte is non-negative\n if (config.basePricePerByte < 0n) {\n throw new PricingError(\n 'basePricePerByte must be non-negative',\n 'INVALID_CONFIG'\n );\n }\n\n // Validate all kindOverrides are non-negative\n if (config.kindOverrides) {\n for (const [kind, price] of config.kindOverrides.entries()) {\n if (price < 0n) {\n throw new PricingError(\n `kindOverride for kind ${kind} must be non-negative`,\n 'INVALID_CONFIG'\n );\n }\n }\n }\n\n this.basePricePerByte = config.basePricePerByte;\n this.kindOverrides = config.kindOverrides ?? new Map();\n }\n\n /**\n * Calculate price for a Nostr event.\n *\n * @param event - The Nostr event to price\n * @returns The calculated price as bigint\n */\n calculatePrice(event: NostrEvent): bigint {\n const toonBytes = encodeEventToToon(event);\n return this.calculatePriceFromBytes(toonBytes, event.kind);\n }\n\n /**\n * Calculate price from raw TOON bytes and event kind.\n *\n * @param bytes - The TOON-encoded event bytes\n * @param kind - The event kind number\n * @returns The calculated price as bigint\n */\n calculatePriceFromBytes(bytes: Uint8Array, kind: number): bigint {\n const pricePerByte = this.getPricePerByte(kind);\n return BigInt(bytes.length) * pricePerByte;\n }\n\n /**\n * Get the effective price per byte for a given kind.\n *\n * @param kind - The event kind number\n * @returns The price per byte (kind override if exists, otherwise base price)\n */\n getPricePerByte(kind: number): bigint {\n return this.kindOverrides.get(kind) ?? this.basePricePerByte;\n }\n}\n","import { readFileSync } from 'fs';\nimport type { PricingConfig } from './types.js';\nimport { PricingError } from './types.js';\n\n/**\n * Load pricing configuration from environment variables.\n *\n * Environment variables:\n * - RELAY_BASE_PRICE_PER_BYTE: Base price per byte (default: \"10\")\n * - RELAY_KIND_OVERRIDES: JSON object mapping kind to price (optional)\n * Format: {\"1\":\"5\",\"30023\":\"100\"}\n *\n * @returns PricingConfig loaded from environment\n * @throws PricingError if env vars contain invalid values\n */\nexport function loadPricingConfigFromEnv(): PricingConfig {\n // Parse base price\n const basePriceStr = process.env['RELAY_BASE_PRICE_PER_BYTE'] ?? '10';\n let basePricePerByte: bigint;\n try {\n basePricePerByte = BigInt(basePriceStr);\n } catch {\n throw new PricingError(\n `Invalid RELAY_BASE_PRICE_PER_BYTE: \"${basePriceStr}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (basePricePerByte < 0n) {\n throw new PricingError(\n `Invalid RELAY_BASE_PRICE_PER_BYTE: value must be non-negative`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n // Parse kind overrides if present\n const kindOverridesStr = process.env['RELAY_KIND_OVERRIDES'];\n let kindOverrides: Map<number, bigint> | undefined;\n\n if (kindOverridesStr) {\n kindOverrides = parseKindOverridesJson(kindOverridesStr);\n }\n\n return {\n basePricePerByte,\n kindOverrides,\n };\n}\n\n/**\n * Load pricing configuration from a JSON file.\n *\n * File format:\n * {\n * \"basePricePerByte\": \"10\",\n * \"kindOverrides\": {\n * \"0\": \"0\",\n * \"1\": \"5\",\n * \"30023\": \"100\"\n * }\n * }\n *\n * @param path - Path to the JSON config file\n * @returns PricingConfig loaded from file\n * @throws PricingError if file cannot be read or contains invalid values\n */\nexport function loadPricingConfigFromFile(path: string): PricingConfig {\n let fileContent: string;\n try {\n fileContent = readFileSync(path, 'utf-8');\n } catch (error) {\n throw new PricingError(\n `Failed to read config file: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'CONFIG_FILE_ERROR'\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fileContent);\n } catch {\n throw new PricingError(\n `Invalid JSON in config file: ${path}`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new PricingError(\n `Config file must contain a JSON object`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n const config = parsed as Record<string, unknown>;\n\n // Parse base price\n const basePriceStr = config['basePricePerByte'];\n if (typeof basePriceStr !== 'string' && typeof basePriceStr !== 'number') {\n throw new PricingError(\n `Config file must contain \"basePricePerByte\" as string or number`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n let basePricePerByte: bigint;\n try {\n basePricePerByte = BigInt(basePriceStr);\n } catch {\n throw new PricingError(\n `Invalid basePricePerByte: \"${basePriceStr}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (basePricePerByte < 0n) {\n throw new PricingError(\n `basePricePerByte must be non-negative`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n // Parse kind overrides if present\n let kindOverrides: Map<number, bigint> | undefined;\n const kindOverridesValue = config['kindOverrides'];\n if (kindOverridesValue !== undefined) {\n if (typeof kindOverridesValue !== 'object' || kindOverridesValue === null) {\n throw new PricingError(\n `kindOverrides must be an object`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n kindOverrides = new Map();\n const overridesObj = kindOverridesValue as Record<string, unknown>;\n\n for (const [kindStr, priceValue] of Object.entries(overridesObj)) {\n const kind = parseInt(kindStr, 10);\n if (isNaN(kind)) {\n throw new PricingError(\n `Invalid kind in kindOverrides: \"${kindStr}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (typeof priceValue !== 'string' && typeof priceValue !== 'number') {\n throw new PricingError(\n `Invalid price for kind ${kind}: must be string or number`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n let price: bigint;\n try {\n price = BigInt(priceValue);\n } catch {\n throw new PricingError(\n `Invalid price for kind ${kind}: \"${priceValue}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (price < 0n) {\n throw new PricingError(\n `Price for kind ${kind} must be non-negative`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n kindOverrides.set(kind, price);\n }\n }\n\n return {\n basePricePerByte,\n kindOverrides,\n };\n}\n\n/**\n * Parse kind overrides JSON string to Map.\n */\nfunction parseKindOverridesJson(jsonStr: string): Map<number, bigint> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonStr);\n } catch {\n throw new PricingError(\n `Invalid JSON in RELAY_KIND_OVERRIDES: ${jsonStr}`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new PricingError(\n `RELAY_KIND_OVERRIDES must be a JSON object`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n const result = new Map<number, bigint>();\n const obj = parsed as Record<string, unknown>;\n\n for (const [kindStr, priceValue] of Object.entries(obj)) {\n const kind = parseInt(kindStr, 10);\n if (isNaN(kind)) {\n throw new PricingError(\n `Invalid kind in RELAY_KIND_OVERRIDES: \"${kindStr}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (typeof priceValue !== 'string' && typeof priceValue !== 'number') {\n throw new PricingError(\n `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: must be string or number`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n let price: bigint;\n try {\n price = BigInt(priceValue);\n } catch {\n throw new PricingError(\n `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: \"${priceValue}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (price < 0n) {\n throw new PricingError(\n `Price for kind ${kind} in RELAY_KIND_OVERRIDES must be non-negative`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n result.set(kind, price);\n }\n\n return result;\n}\n","/**\n * Subscribe to upstream relays and propagate events into the local EventStore.\n *\n * Follows the same lifecycle pattern as core's discovery tracker and SocialPeerDiscovery:\n * - Accept optional SimplePool for testability\n * - start() returns { unsubscribe } cleanup handle\n * - isUnsubscribed guard prevents processing after teardown\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\nimport type { EventStore } from '../storage/index.js';\n\n/**\n * Configuration for RelaySubscriber.\n */\nexport interface RelaySubscriberConfig {\n /** Upstream relay URLs to subscribe to */\n relayUrls: string[];\n /** Nostr filter for which events to pull (e.g. kinds, authors) */\n filter: Filter;\n /** Verify event signatures before storing (default: true) */\n verifySignatures?: boolean;\n}\n\n/**\n * Subscribes to upstream Nostr relays and stores received events\n * in the local EventStore. Useful for relay-to-relay event propagation.\n */\nexport class RelaySubscriber {\n private readonly config: RelaySubscriberConfig;\n private readonly eventStore: EventStore;\n private readonly pool: SimplePool;\n private started = false;\n\n /**\n * @param config - Subscriber configuration\n * @param eventStore - Storage backend to write events into\n * @param pool - Optional SimplePool instance (creates new one if not provided)\n */\n constructor(\n config: RelaySubscriberConfig,\n eventStore: EventStore,\n pool?: SimplePool\n ) {\n this.config = config;\n this.eventStore = eventStore;\n this.pool = pool ?? new SimplePool();\n }\n\n /**\n * Start subscribing to the configured upstream relays.\n *\n * @returns Handle with unsubscribe() to stop the subscription\n * @throws Error if already started\n */\n start(): { unsubscribe: () => void } {\n if (this.started) {\n throw new Error('RelaySubscriber already started');\n }\n this.started = true;\n\n const shouldVerify = this.config.verifySignatures !== false;\n let isUnsubscribed = false;\n\n const subCloser = this.pool.subscribeMany(\n this.config.relayUrls,\n this.config.filter,\n {\n onevent: (event: NostrEvent) => {\n if (isUnsubscribed) return;\n\n if (shouldVerify && !verifyEvent(event)) {\n return;\n }\n\n try {\n this.eventStore.store(event);\n } catch (error) {\n console.warn(\n '[RelaySubscriber] Failed to store event:',\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n },\n }\n );\n\n return {\n unsubscribe: () => {\n if (!isUnsubscribed) {\n isUnsubscribed = true;\n subCloser.close();\n this.started = false;\n }\n },\n };\n }\n}\n","/**\n * @toon-protocol/relay\n *\n * ILP-gated Nostr relay with Business Logic Server.\n */\n\nexport const VERSION = '0.1.0';\n\n// Types\nexport type { RelayConfig } from './types.js';\nexport { DEFAULT_RELAY_CONFIG } from './types.js';\n\n// Storage\nexport type { EventStore } from './storage/index.js';\nexport {\n InMemoryEventStore,\n SqliteEventStore,\n RelayError,\n} from './storage/index.js';\n\n// Filters\nexport { matchFilter } from './filters/index.js';\n\n// WebSocket\nexport type { Subscription } from './websocket/index.js';\nexport { ConnectionHandler, NostrRelayServer } from './websocket/index.js';\n\n// TOON encoding/decoding\nexport {\n encodeEventToToon,\n decodeEventFromToon,\n ToonEncodeError,\n ToonDecodeError,\n} from './toon/index.js';\n\n// Business Logic Server\nexport type {\n BlsConfig,\n HandlePacketRequest,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n HandlePacketResponse,\n} from './bls/index.js';\nexport {\n BlsError,\n ILP_ERROR_CODES,\n BusinessLogicServer,\n generateFulfillment,\n isValidPubkey,\n} from './bls/index.js';\n\n// Pricing\nexport type { PricingConfig } from './pricing/index.js';\nexport {\n PricingError,\n PricingService,\n loadPricingConfigFromEnv,\n loadPricingConfigFromFile,\n} from './pricing/index.js';\n\n// Subscriber\nexport type { RelaySubscriberConfig } from './subscriber/index.js';\nexport { RelaySubscriber } from './subscriber/index.js';\n\n// Re-exports from @toon-protocol/bls removed to avoid circular dependency\n// Downstream consumers should import directly from @toon-protocol/bls instead\n"],"mappings":"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","names":["verifyEvent"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/filters/matchFilter.ts","../src/storage/InMemoryEventStore.ts","../src/storage/SqliteEventStore.ts","../src/toon/index.ts","../src/websocket/ConnectionHandler.ts","../src/websocket/NostrRelayServer.ts","../src/bls/types.ts","../src/bls/BusinessLogicServer.ts","../src/pricing/types.ts","../src/pricing/PricingService.ts","../src/pricing/config.ts","../src/subscriber/RelaySubscriber.ts","../src/index.ts"],"sourcesContent":["/**\n * Configuration options for the Nostr relay.\n */\nexport interface RelayConfig {\n /** Port to listen on (default: 7000) */\n port: number;\n /** Maximum concurrent connections (default: 100) */\n maxConnections?: number;\n /** Maximum subscriptions per connection (default: 20) */\n maxSubscriptionsPerConnection?: number;\n /** Maximum filters per subscription (default: 10) */\n maxFiltersPerSubscription?: number;\n /** Path to SQLite database file (default: ':memory:' for in-memory) */\n databasePath?: string;\n}\n\n/**\n * Default relay configuration values.\n */\nexport const DEFAULT_RELAY_CONFIG: Required<RelayConfig> = {\n port: 7000,\n maxConnections: 100,\n maxSubscriptionsPerConnection: 20,\n maxFiltersPerSubscription: 10,\n databasePath: ':memory:',\n};\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\n\n/**\n * Check if an event matches a single filter according to NIP-01 rules.\n *\n * Matching rules:\n * - All specified fields must match (AND logic)\n * - `ids` and `authors` support prefix matching\n * - Tag filters (#e, #p, etc.) match events with corresponding tags\n * - Empty filter matches all events\n *\n * @param event - The Nostr event to check\n * @param filter - The filter to match against\n * @returns true if the event matches the filter\n */\nexport function matchFilter(event: NostrEvent, filter: Filter): boolean {\n // Empty filter matches everything\n if (Object.keys(filter).length === 0) {\n return true;\n }\n\n // Check ids (prefix matching)\n if (filter.ids !== undefined && filter.ids.length > 0) {\n const matches = filter.ids.some((id) => event.id.startsWith(id));\n if (!matches) return false;\n }\n\n // Check authors (prefix matching)\n if (filter.authors !== undefined && filter.authors.length > 0) {\n const matches = filter.authors.some((author) =>\n event.pubkey.startsWith(author)\n );\n if (!matches) return false;\n }\n\n // Check kinds (exact matching)\n if (filter.kinds !== undefined && filter.kinds.length > 0) {\n if (!filter.kinds.includes(event.kind)) return false;\n }\n\n // Check since (created_at >= since)\n if (filter.since !== undefined) {\n if (event.created_at < filter.since) return false;\n }\n\n // Check until (created_at <= until)\n if (filter.until !== undefined) {\n if (event.created_at > filter.until) return false;\n }\n\n // Check tag filters (#e, #p, and generic #<single-letter>)\n for (const key of Object.keys(filter)) {\n if (key.startsWith('#') && key.length === 2) {\n const tagName = key.slice(1);\n const filterValues = filter[key as `#${string}`];\n\n if (filterValues !== undefined && filterValues.length > 0) {\n // Find matching tags in the event\n const eventTagValues = event.tags\n .filter((tag) => tag[0] === tagName)\n .map((tag) => tag[1]);\n\n // At least one filter value must match an event tag value\n const hasMatch = filterValues.some((v) => eventTagValues.includes(v));\n if (!hasMatch) return false;\n }\n }\n }\n\n return true;\n}\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\nimport { matchFilter } from '../filters/index.js';\n\n/**\n * Interface for event storage backends.\n */\nexport interface EventStore {\n /** Store an event by its ID */\n store(event: NostrEvent): void;\n /** Retrieve a single event by ID */\n get(id: string): NostrEvent | undefined;\n /** Query events matching any of the provided filters */\n query(filters: Filter[]): NostrEvent[];\n /** Close the storage backend (optional) */\n close?(): void;\n}\n\n/**\n * In-memory implementation of EventStore.\n * Events are stored in a Map keyed by event ID.\n */\nexport class InMemoryEventStore implements EventStore {\n private events = new Map<string, NostrEvent>();\n\n store(event: NostrEvent): void {\n this.events.set(event.id, event);\n }\n\n get(id: string): NostrEvent | undefined {\n return this.events.get(id);\n }\n\n query(filters: Filter[]): NostrEvent[] {\n // Get all events\n const allEvents = Array.from(this.events.values());\n\n // If no filters provided, return all events sorted by created_at desc\n if (filters.length === 0) {\n return allEvents.sort((a, b) => b.created_at - a.created_at);\n }\n\n // Find events matching ANY filter (OR logic between filters)\n const matchingEvents: NostrEvent[] = [];\n\n for (const event of allEvents) {\n for (const filter of filters) {\n if (matchFilter(event, filter)) {\n matchingEvents.push(event);\n break; // Only add once even if matches multiple filters\n }\n }\n }\n\n // Sort by created_at descending\n matchingEvents.sort((a, b) => b.created_at - a.created_at);\n\n // Apply limit from first filter that has one (NIP-01 semantics)\n const limitFilter = filters.find((f) => f.limit !== undefined);\n if (limitFilter?.limit !== undefined) {\n return matchingEvents.slice(0, limitFilter.limit);\n }\n\n return matchingEvents;\n }\n\n /**\n * Close the storage backend (no-op for in-memory store).\n */\n close(): void {\n // No-op for in-memory store\n }\n}\n","import Database from 'better-sqlite3';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\nimport type { EventStore } from './InMemoryEventStore.js';\n\n/**\n * SQL schema for the events table.\n */\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n pubkey TEXT NOT NULL,\n kind INTEGER NOT NULL,\n content TEXT NOT NULL,\n tags TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n sig TEXT NOT NULL,\n received_at INTEGER NOT NULL\n)\n`;\n\n/**\n * SQL for creating indexes on the events table.\n */\nconst INDEX_SQL = [\n 'CREATE INDEX IF NOT EXISTS idx_events_pubkey ON events(pubkey)',\n 'CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind)',\n 'CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_events_pubkey_kind ON events(pubkey, kind)',\n];\n\n/**\n * Initialize the database schema.\n */\nfunction initializeSchema(db: Database.Database): void {\n db.exec(SCHEMA_SQL);\n for (const indexSql of INDEX_SQL) {\n db.exec(indexSql);\n }\n}\n\n/**\n * Custom error class for relay storage errors.\n */\nexport class RelayError extends Error {\n constructor(\n message: string,\n public code: string\n ) {\n super(message);\n this.name = 'RelayError';\n }\n}\n\n/**\n * Check if an event kind is in the replaceable range (10000-19999).\n */\nfunction isReplaceableKind(kind: number): boolean {\n return kind >= 10000 && kind <= 19999;\n}\n\n/**\n * Check if an event kind is in the parameterized replaceable range (30000-39999).\n */\nfunction isParameterizedReplaceableKind(kind: number): boolean {\n return kind >= 30000 && kind <= 39999;\n}\n\n/**\n * Get the 'd' tag value from an event's tags array.\n */\nfunction getDTagValue(tags: string[][]): string {\n const dTag = tags.find((tag) => tag[0] === 'd');\n return dTag?.[1] ?? '';\n}\n\n/**\n * SQLite implementation of EventStore.\n * Persists events to a SQLite database file.\n */\nexport class SqliteEventStore implements EventStore {\n private db: Database.Database;\n private insertStmt: Database.Statement;\n private getStmt: Database.Statement;\n private deleteByPubkeyKindStmt: Database.Statement;\n private deleteByPubkeyKindDTagStmt: Database.Statement;\n private getByPubkeyKindStmt: Database.Statement;\n private getByPubkeyKindDTagStmt: Database.Statement;\n\n /**\n * Create a new SqliteEventStore.\n * @param dbPath - Path to the database file. Use ':memory:' for in-memory database.\n */\n constructor(dbPath = ':memory:') {\n try {\n this.db = new Database(dbPath);\n initializeSchema(this.db);\n\n // Prepare statements for better performance\n this.insertStmt = this.db.prepare(`\n INSERT OR REPLACE INTO events (id, pubkey, kind, content, tags, created_at, sig, received_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n this.getStmt = this.db.prepare('SELECT * FROM events WHERE id = ?');\n\n this.deleteByPubkeyKindStmt = this.db.prepare(\n 'DELETE FROM events WHERE pubkey = ? AND kind = ?'\n );\n\n this.deleteByPubkeyKindDTagStmt = this.db.prepare(\n \"DELETE FROM events WHERE pubkey = ? AND kind = ? AND json_extract(tags, '$') LIKE ?\"\n );\n\n this.getByPubkeyKindStmt = this.db.prepare(\n 'SELECT id, created_at FROM events WHERE pubkey = ? AND kind = ?'\n );\n\n this.getByPubkeyKindDTagStmt = this.db.prepare(\n 'SELECT id, created_at FROM events WHERE pubkey = ? AND kind = ? AND tags LIKE ?'\n );\n } catch (error) {\n throw new RelayError(\n `Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Store an event in the database.\n * Handles replaceable and parameterized replaceable events according to NIP-01.\n */\n store(event: NostrEvent): void {\n try {\n const tagsJson = JSON.stringify(event.tags);\n const receivedAt = Math.floor(Date.now() / 1000);\n\n if (isReplaceableKind(event.kind)) {\n // Replaceable event (10000-19999)\n this.storeReplaceableEvent(event, tagsJson, receivedAt);\n } else if (isParameterizedReplaceableKind(event.kind)) {\n // Parameterized replaceable event (30000-39999)\n this.storeParameterizedReplaceableEvent(event, tagsJson, receivedAt);\n } else {\n // Regular event - INSERT OR IGNORE to handle duplicates\n const insertOrIgnore = this.db.prepare(`\n INSERT OR IGNORE INTO events (id, pubkey, kind, content, tags, created_at, sig, received_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n insertOrIgnore.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n }\n } catch (error) {\n if (error instanceof RelayError) {\n throw error;\n }\n throw new RelayError(\n `Failed to store event: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Store a replaceable event (kinds 10000-19999).\n * Only keeps the latest event per pubkey+kind.\n */\n private storeReplaceableEvent(\n event: NostrEvent,\n tagsJson: string,\n receivedAt: number\n ): void {\n const existing = this.getByPubkeyKindStmt.get(event.pubkey, event.kind) as\n | { id: string; created_at: number }\n | undefined;\n\n if (existing) {\n // Only replace if new event is newer, or same time with lower id\n if (\n event.created_at > existing.created_at ||\n (event.created_at === existing.created_at && event.id < existing.id)\n ) {\n // Use transaction for atomicity\n const transaction = this.db.transaction(() => {\n this.deleteByPubkeyKindStmt.run(event.pubkey, event.kind);\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n });\n transaction();\n }\n // If existing event is newer or same, don't replace\n } else {\n // No existing event, just insert\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n }\n }\n\n /**\n * Store a parameterized replaceable event (kinds 30000-39999).\n * Only keeps the latest event per pubkey+kind+d-tag.\n */\n private storeParameterizedReplaceableEvent(\n event: NostrEvent,\n tagsJson: string,\n receivedAt: number\n ): void {\n const dTagValue = getDTagValue(event.tags);\n\n // For empty d-tag value, we need to match events that either:\n // 1. Have [\"d\", \"\"] in tags\n // 2. Have no d-tag at all (tags doesn't contain \"d\" as first element)\n let existing: { id: string; created_at: number } | undefined;\n\n if (dTagValue === '') {\n // Query for events with same pubkey and kind, then filter in code\n const candidates = this.db\n .prepare(\n 'SELECT id, created_at, tags FROM events WHERE pubkey = ? AND kind = ?'\n )\n .all(event.pubkey, event.kind) as {\n id: string;\n created_at: number;\n tags: string;\n }[];\n\n // Find one with empty or missing d-tag\n for (const candidate of candidates) {\n const candidateTags = JSON.parse(candidate.tags) as string[][];\n const candidateDTagValue = getDTagValue(candidateTags);\n if (candidateDTagValue === '') {\n existing = { id: candidate.id, created_at: candidate.created_at };\n break;\n }\n }\n } else {\n const dTagPattern = `%[\"d\",\"${dTagValue}\"%`;\n existing = this.getByPubkeyKindDTagStmt.get(\n event.pubkey,\n event.kind,\n dTagPattern\n ) as { id: string; created_at: number } | undefined;\n }\n\n if (existing) {\n // Only replace if new event is newer, or same time with lower id\n if (\n event.created_at > existing.created_at ||\n (event.created_at === existing.created_at && event.id < existing.id)\n ) {\n // Use transaction for atomicity - delete by ID for safety\n const transaction = this.db.transaction(() => {\n this.db.prepare('DELETE FROM events WHERE id = ?').run(existing.id);\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n });\n transaction();\n }\n // If existing event is newer or same, don't replace\n } else {\n // No existing event, just insert\n this.insertStmt.run(\n event.id,\n event.pubkey,\n event.kind,\n event.content,\n tagsJson,\n event.created_at,\n event.sig,\n receivedAt\n );\n }\n }\n\n /**\n * Retrieve an event by its ID.\n */\n get(id: string): NostrEvent | undefined {\n try {\n const row = this.getStmt.get(id) as\n | {\n id: string;\n pubkey: string;\n kind: number;\n content: string;\n tags: string;\n created_at: number;\n sig: string;\n }\n | undefined;\n\n if (!row) {\n return undefined;\n }\n\n return {\n id: row.id,\n pubkey: row.pubkey,\n kind: row.kind,\n content: row.content,\n tags: JSON.parse(row.tags) as string[][],\n created_at: row.created_at,\n sig: row.sig,\n };\n } catch (error) {\n throw new RelayError(\n `Failed to get event: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Query events matching any of the provided filters.\n */\n query(filters: Filter[]): NostrEvent[] {\n try {\n const { sql, params } = this.buildQuerySql(filters);\n const stmt = this.db.prepare(sql);\n const rows = stmt.all(...params) as {\n id: string;\n pubkey: string;\n kind: number;\n content: string;\n tags: string;\n created_at: number;\n sig: string;\n }[];\n\n return rows.map((row) => ({\n id: row.id,\n pubkey: row.pubkey,\n kind: row.kind,\n content: row.content,\n tags: JSON.parse(row.tags) as string[][],\n created_at: row.created_at,\n sig: row.sig,\n }));\n } catch (error) {\n throw new RelayError(\n `Failed to query events: ${error instanceof Error ? error.message : String(error)}`,\n 'STORAGE_ERROR'\n );\n }\n }\n\n /**\n * Build SQL query from filters.\n */\n private buildQuerySql(filters: Filter[]): { sql: string; params: unknown[] } {\n if (filters.length === 0) {\n return {\n sql: 'SELECT * FROM events ORDER BY created_at DESC',\n params: [],\n };\n }\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n for (const filter of filters) {\n const filterConditions: string[] = [];\n\n if (filter.ids?.length) {\n // Prefix matching with LIKE\n const idConditions = filter.ids.map(() => 'id LIKE ?');\n filterConditions.push(`(${idConditions.join(' OR ')})`);\n params.push(...filter.ids.map((id) => `${id}%`));\n }\n\n if (filter.authors?.length) {\n const authorConditions = filter.authors.map(() => 'pubkey LIKE ?');\n filterConditions.push(`(${authorConditions.join(' OR ')})`);\n params.push(...filter.authors.map((a) => `${a}%`));\n }\n\n if (filter.kinds?.length) {\n filterConditions.push(\n `kind IN (${filter.kinds.map(() => '?').join(', ')})`\n );\n params.push(...filter.kinds);\n }\n\n if (filter.since !== undefined) {\n filterConditions.push('created_at >= ?');\n params.push(filter.since);\n }\n\n if (filter.until !== undefined) {\n filterConditions.push('created_at <= ?');\n params.push(filter.until);\n }\n\n // Handle tag filters (#e, #p, etc.)\n for (const [key, values] of Object.entries(filter)) {\n if (key.startsWith('#') && Array.isArray(values) && values.length > 0) {\n const tagName = key.slice(1);\n const tagConditions = values.map(() => `tags LIKE ?`);\n filterConditions.push(`(${tagConditions.join(' OR ')})`);\n params.push(...values.map((v) => `%[\"${tagName}\",\"${v}\"%`));\n }\n }\n\n if (filterConditions.length > 0) {\n conditions.push(`(${filterConditions.join(' AND ')})`);\n }\n }\n\n let sql = 'SELECT * FROM events';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' OR ')}`;\n }\n sql += ' ORDER BY created_at DESC';\n\n // Apply limit from first filter that specifies it\n const limitFilter = filters.find((f) => f.limit !== undefined);\n if (limitFilter?.limit !== undefined) {\n sql += ' LIMIT ?';\n params.push(limitFilter.limit);\n }\n\n return { sql, params };\n }\n\n /**\n * Close the database connection.\n */\n close(): void {\n this.db.close();\n }\n}\n","export {\n encodeEventToToon,\n encodeEventToToonString,\n ToonEncodeError,\n} from '@toon-protocol/core';\nexport { decodeEventFromToon, ToonDecodeError } from '@toon-protocol/core';\n","import type { WebSocket } from 'ws';\nimport type { Filter } from 'nostr-tools/filter';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport type { RelayConfig } from '../types.js';\nimport { DEFAULT_RELAY_CONFIG } from '../types.js';\nimport { encodeEventToToonString } from '../toon/index.js';\nimport { matchFilter } from '../filters/index.js';\n\n/**\n * Represents an active subscription from a client.\n */\nexport interface Subscription {\n /** Unique subscription identifier from the client */\n id: string;\n /** Filters applied to this subscription */\n filters: Filter[];\n}\n\n/**\n * Handles NIP-01 messages for a single WebSocket connection.\n */\nexport class ConnectionHandler {\n private subscriptions = new Map<string, Subscription>();\n private config: Required<RelayConfig>;\n\n constructor(\n private ws: WebSocket,\n private eventStore: EventStore,\n config: Partial<RelayConfig> = {}\n ) {\n this.config = { ...DEFAULT_RELAY_CONFIG, ...config };\n }\n\n /**\n * Handle an incoming message from the WebSocket.\n */\n handleMessage(data: string): void {\n console.log(`[ConnectionHandler] Received message:`, data.slice(0, 150));\n let message: unknown[];\n\n try {\n const parsed = JSON.parse(data);\n if (!Array.isArray(parsed)) {\n this.sendNotice('error: invalid message format, expected JSON array');\n return;\n }\n message = parsed;\n } catch {\n this.sendNotice('error: invalid JSON');\n return;\n }\n\n const messageType = message[0];\n console.log(`[ConnectionHandler] Message type: ${messageType}`);\n\n if (messageType === 'REQ') {\n const subscriptionId = message[1];\n const filters = message.slice(2) as Filter[];\n this.handleReq(subscriptionId as string, filters);\n } else if (messageType === 'EVENT') {\n const event = message[1];\n this.handleEvent(event as NostrEvent);\n } else if (messageType === 'CLOSE') {\n const subscriptionId = message[1];\n this.handleClose(subscriptionId as string);\n } else {\n this.sendNotice(`error: unknown message type: ${messageType}`);\n }\n }\n\n /**\n * Handle a REQ message to create/update a subscription.\n */\n private handleReq(subscriptionId: string, filters: Filter[]): void {\n // Validate subscription ID\n if (typeof subscriptionId !== 'string' || subscriptionId.length === 0) {\n this.sendNotice('error: invalid subscription id');\n return;\n }\n\n // Check subscription limit (only for new subscriptions)\n if (!this.subscriptions.has(subscriptionId)) {\n if (\n this.subscriptions.size >= this.config.maxSubscriptionsPerConnection\n ) {\n this.sendNotice('error: too many subscriptions');\n return;\n }\n }\n\n // Check filter limit\n if (filters.length > this.config.maxFiltersPerSubscription) {\n this.sendNotice('error: too many filters');\n return;\n }\n\n // Store the subscription\n this.subscriptions.set(subscriptionId, {\n id: subscriptionId,\n filters,\n });\n\n // Query matching events\n console.log(\n `[ConnectionHandler] REQ: ${subscriptionId}, filters:`,\n JSON.stringify(filters).slice(0, 100)\n );\n const events = this.eventStore.query(filters);\n console.log(\n `[ConnectionHandler] Query returned ${events.length} events for ${subscriptionId}`\n );\n\n // Send matching events\n for (const event of events) {\n console.log(\n `[ConnectionHandler] Sending event ${event.id.slice(0, 16)}... to ${subscriptionId}`\n );\n this.sendEvent(subscriptionId, event);\n }\n\n // Send EOSE\n console.log(`[ConnectionHandler] Sending EOSE for ${subscriptionId}`);\n this.sendEose(subscriptionId);\n }\n\n /**\n * Handle an EVENT message from a WebSocket client.\n *\n * Rejects all external writes — the relay is ILP-gated (pay to write).\n * Events are only stored through the ILP packet handler which calls\n * eventStore.store() directly and then broadcastEvent() to notify subscribers.\n */\n private handleEvent(event: NostrEvent): void {\n this.sendOk(\n event.id,\n false,\n 'restricted: writes require ILP payment'\n );\n }\n\n /**\n * Handle a CLOSE message to terminate a subscription.\n */\n private handleClose(subscriptionId: string): void {\n // Silently remove subscription (no error if it doesn't exist per NIP-01)\n this.subscriptions.delete(subscriptionId);\n }\n\n /**\n * Push a new event to all matching subscriptions on this connection.\n * Used when events are stored outside the WebSocket flow (e.g., via ILP).\n */\n notifyNewEvent(event: NostrEvent): void {\n for (const sub of this.subscriptions.values()) {\n const matches = sub.filters.some((f) => matchFilter(event, f));\n if (matches) {\n this.sendEvent(sub.id, event);\n }\n }\n }\n\n /**\n * Clean up all subscriptions for this connection.\n */\n cleanup(): void {\n this.subscriptions.clear();\n }\n\n /**\n * Get the number of active subscriptions.\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n private sendEvent(subscriptionId: string, event: NostrEvent): void {\n this.send(['EVENT', subscriptionId, encodeEventToToonString(event)]);\n }\n\n private sendEose(subscriptionId: string): void {\n this.send(['EOSE', subscriptionId]);\n }\n\n private sendOk(eventId: string, success: boolean, message: string): void {\n this.send(['OK', eventId, success, message]);\n }\n\n private sendNotice(message: string): void {\n this.send(['NOTICE', message]);\n }\n\n private send(message: unknown[]): void {\n if (this.ws.readyState === 1) {\n // OPEN\n this.ws.send(JSON.stringify(message));\n }\n }\n}\n","import type { WebSocket } from 'ws';\nimport { WebSocketServer } from 'ws';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport type { RelayConfig } from '../types.js';\nimport { DEFAULT_RELAY_CONFIG } from '../types.js';\nimport { ConnectionHandler } from './ConnectionHandler.js';\n\n/**\n * A NIP-01 compliant Nostr relay WebSocket server.\n * Handles client connections and routes messages to ConnectionHandlers.\n */\nexport class NostrRelayServer {\n private wss: WebSocketServer | null = null;\n private handlers = new Map<WebSocket, ConnectionHandler>();\n private config: Required<RelayConfig>;\n\n constructor(\n config: Partial<RelayConfig> = {},\n private eventStore: EventStore\n ) {\n this.config = { ...DEFAULT_RELAY_CONFIG, ...config };\n }\n\n /**\n * Start the WebSocket server.\n */\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.wss = new WebSocketServer({ port: this.config.port });\n\n this.wss.on('connection', (ws: WebSocket) => {\n this.handleConnection(ws);\n });\n\n this.wss.on('error', (error: Error) => {\n console.error('[NostrRelayServer] Server error:', error.message);\n });\n\n this.wss.on('listening', () => {\n const address = this.wss?.address();\n if (address && typeof address === 'object') {\n console.log(`[NostrRelayServer] Listening on port ${address.port}`);\n }\n resolve();\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Stop the WebSocket server and close all connections.\n */\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (!this.wss) {\n resolve();\n return;\n }\n\n // Clean up all connection handlers\n for (const [ws, handler] of this.handlers) {\n handler.cleanup();\n ws.close();\n }\n this.handlers.clear();\n\n this.wss.close(() => {\n this.wss = null;\n resolve();\n });\n });\n }\n\n /**\n * Get the port the server is listening on.\n * Returns 0 if the server is not started.\n */\n getPort(): number {\n if (!this.wss) return 0;\n const address = this.wss.address();\n if (address && typeof address === 'object') {\n return address.port;\n }\n return 0;\n }\n\n /**\n * Get the number of connected clients.\n */\n getClientCount(): number {\n return this.handlers.size;\n }\n\n /**\n * Broadcast an event to all connected clients with matching subscriptions.\n * Call this after storing an event outside the WebSocket flow (e.g., via ILP)\n * so that discovery subscribers are notified.\n */\n broadcastEvent(event: NostrEvent): void {\n for (const handler of this.handlers.values()) {\n handler.notifyNewEvent(event);\n }\n }\n\n private handleConnection(ws: WebSocket): void {\n // Check max connections\n if (this.handlers.size >= this.config.maxConnections) {\n ws.close(1013, 'max connections reached');\n return;\n }\n\n console.log('[NostrRelayServer] Client connected');\n\n const handler = new ConnectionHandler(ws, this.eventStore, this.config);\n this.handlers.set(ws, handler);\n\n ws.on('message', (data: Buffer | string) => {\n const message = typeof data === 'string' ? data : data.toString();\n handler.handleMessage(message);\n });\n\n ws.on('close', () => {\n console.log('[NostrRelayServer] Client disconnected');\n handler.cleanup();\n this.handlers.delete(ws);\n });\n\n ws.on('error', (error: Error) => {\n console.error('[NostrRelayServer] Client error:', error.message);\n handler.cleanup();\n this.handlers.delete(ws);\n });\n }\n}\n","import { RelayError } from '../storage/index.js';\nimport type { PricingService } from '../pricing/index.js';\n\n/**\n * Regex for validating Nostr pubkeys (64 lowercase hex characters).\n */\nexport const PUBKEY_REGEX = /^[0-9a-f]{64}$/;\n\n/**\n * Validate that a string is a valid Nostr pubkey format.\n * @param pubkey - The pubkey to validate\n * @returns true if valid 64-character lowercase hex string\n */\nexport function isValidPubkey(pubkey: string): boolean {\n return PUBKEY_REGEX.test(pubkey);\n}\n\n/**\n * Configuration for the Business Logic Server.\n */\nexport interface BlsConfig {\n /** Base price per byte for event storage (used for simple pricing) */\n basePricePerByte: bigint;\n /** Optional PricingService for kind-based pricing overrides */\n pricingService?: PricingService;\n /** Optional owner pubkey - events from this pubkey bypass payment */\n ownerPubkey?: string;\n}\n\n/**\n * Incoming packet request from ILP connector.\n */\nexport interface HandlePacketRequest {\n /** Payment amount as string (parsed to bigint) */\n amount: string;\n /** ILP destination address */\n destination: string;\n /** Base64-encoded TOON Nostr event */\n data: string;\n /** Source ILP address */\n sourceAccount?: string;\n}\n\n/**\n * Response for accepted packet.\n */\nexport interface HandlePacketAcceptResponse {\n accept: true;\n /** @deprecated Connector computes fulfillment from SHA256(toon_bytes). Will be removed in a future version. */\n fulfillment?: string;\n metadata?: {\n eventId: string;\n storedAt: number;\n };\n}\n\n/**\n * Response for rejected packet.\n */\nexport interface HandlePacketRejectResponse {\n accept: false;\n /** ILP error code (F00, F06, etc.) */\n code: string;\n /** Human-readable error message */\n message: string;\n metadata?: {\n required?: string;\n received?: string;\n };\n}\n\n/**\n * Union type for packet response.\n */\nexport type HandlePacketResponse =\n | HandlePacketAcceptResponse\n | HandlePacketRejectResponse;\n\n/**\n * ILP error code constants.\n */\nexport const ILP_ERROR_CODES = {\n BAD_REQUEST: 'F00',\n INSUFFICIENT_AMOUNT: 'F06',\n INTERNAL_ERROR: 'T00',\n} as const;\n\n/**\n * BLS-specific error class.\n */\nexport class BlsError extends RelayError {\n constructor(message: string, code = 'BLS_ERROR') {\n super(message, code);\n this.name = 'BlsError';\n }\n}\n","import { createHash } from 'crypto';\nimport { Hono } from 'hono';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { EventStore } from '../storage/index.js';\nimport { decodeEventFromToon } from '../toon/index.js';\nimport type {\n BlsConfig,\n HandlePacketRequest,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n} from './types.js';\nimport { ILP_ERROR_CODES, BlsError, isValidPubkey } from './types.js';\n\n/**\n * Generate a fulfillment from an event ID.\n * The fulfillment is SHA-256(eventId) encoded as base64.\n *\n * Note: The sender must use SHA256(SHA256(eventId)) as the condition\n * in their ILP Prepare packet.\n *\n * @deprecated Connector computes fulfillment from SHA256(toon_bytes). BLS should not generate fulfillment.\n */\nexport function generateFulfillment(eventId: string): string {\n const hash = createHash('sha256').update(eventId).digest();\n return hash.toString('base64');\n}\n\n/**\n * Business Logic Server for ILP payment verification.\n *\n * Handles payment requests from an ILP connector, verifying that the\n * payment amount meets the required price for storing the included\n * Nostr event.\n */\nexport class BusinessLogicServer {\n private app: Hono;\n\n constructor(\n private config: BlsConfig,\n private eventStore: EventStore\n ) {\n // Validate ownerPubkey format if provided\n if (\n config.ownerPubkey !== undefined &&\n !isValidPubkey(config.ownerPubkey)\n ) {\n throw new BlsError(\n 'Invalid ownerPubkey format: must be 64 lowercase hex characters',\n 'INVALID_CONFIG'\n );\n }\n this.app = new Hono();\n this.setupRoutes();\n }\n\n /**\n * Set up HTTP routes.\n */\n private setupRoutes(): void {\n this.app.post('/handle-packet', async (c) => {\n try {\n const body = await c.req.json<HandlePacketRequest>();\n const response = this.handlePacket(body);\n return c.json(response, response.accept ? 200 : 400);\n } catch (error) {\n const response: HandlePacketRejectResponse = {\n accept: false,\n code: ILP_ERROR_CODES.INTERNAL_ERROR,\n message:\n error instanceof Error ? error.message : 'Internal server error',\n };\n return c.json(response, 500);\n }\n });\n\n this.app.get('/health', (c) => {\n return c.json({\n status: 'healthy',\n timestamp: Date.now(),\n });\n });\n }\n\n /**\n * Process a packet request.\n *\n * This method is public to support direct connector integration in embedded mode,\n * where the connector calls this method directly via setPacketHandler() instead\n * of making HTTP requests.\n */\n public handlePacket(\n request: HandlePacketRequest\n ): HandlePacketAcceptResponse | HandlePacketRejectResponse {\n // Validate required fields\n if (!request.amount || !request.destination || !request.data) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Missing required fields: amount, destination, data',\n };\n }\n\n // Decode base64 data\n let toonBytes: Uint8Array;\n try {\n toonBytes = Uint8Array.from(Buffer.from(request.data, 'base64'));\n } catch {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid base64 encoding in data field',\n };\n }\n\n // Decode TOON to Nostr event\n let event;\n try {\n event = decodeEventFromToon(toonBytes);\n } catch (error) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: `Invalid TOON data: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n\n // Verify event signature\n if (!verifyEvent(event)) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid event signature',\n };\n }\n\n // Self-write bypass: owner events skip payment verification\n if (this.config.ownerPubkey && event.pubkey === this.config.ownerPubkey) {\n try {\n this.eventStore.store(event);\n } catch (error) {\n throw new BlsError(\n `Failed to store event: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'STORAGE_ERROR'\n );\n }\n\n return {\n accept: true,\n fulfillment: generateFulfillment(event.id),\n metadata: {\n eventId: event.id,\n storedAt: Date.now(),\n },\n };\n }\n\n // Calculate price: use PricingService if provided, otherwise simple calculation\n const price = this.config.pricingService\n ? this.config.pricingService.calculatePriceFromBytes(\n toonBytes,\n event.kind\n )\n : BigInt(toonBytes.length) * this.config.basePricePerByte;\n\n // Parse and compare amounts\n let amount: bigint;\n try {\n amount = BigInt(request.amount);\n } catch {\n return {\n accept: false,\n code: ILP_ERROR_CODES.BAD_REQUEST,\n message: 'Invalid amount format',\n };\n }\n\n if (amount < price) {\n return {\n accept: false,\n code: ILP_ERROR_CODES.INSUFFICIENT_AMOUNT,\n message: 'Insufficient payment amount',\n metadata: {\n required: price.toString(),\n received: amount.toString(),\n },\n };\n }\n\n // Store event\n try {\n this.eventStore.store(event);\n } catch (error) {\n throw new BlsError(\n `Failed to store event: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'STORAGE_ERROR'\n );\n }\n\n // Generate fulfillment and return success\n const storedAt = Date.now();\n return {\n accept: true,\n fulfillment: generateFulfillment(event.id),\n metadata: {\n eventId: event.id,\n storedAt,\n },\n };\n }\n\n /**\n * Get the Hono app instance for testing or composition.\n */\n getApp(): Hono {\n return this.app;\n }\n\n /**\n * Start the HTTP server on the specified port.\n */\n start(port: number): void {\n // Note: In Node.js, you would typically use @hono/node-server\n // For now, this method is a placeholder for actual server startup\n // The actual server binding would be done by the consumer\n console.log(`BLS would start on port ${port}`);\n }\n}\n","import { RelayError } from '../storage/index.js';\n\n/**\n * Configuration for the Pricing Service.\n */\nexport interface PricingConfig {\n /** Base price per byte for event storage */\n basePricePerByte: bigint;\n /** Optional price overrides by event kind */\n kindOverrides?: Map<number, bigint>;\n}\n\n/**\n * Error class for pricing-specific errors.\n */\nexport class PricingError extends RelayError {\n constructor(message: string, code = 'PRICING_ERROR') {\n super(message, code);\n this.name = 'PricingError';\n }\n}\n","import type { NostrEvent } from 'nostr-tools/pure';\nimport { encodeEventToToon } from '../toon/index.js';\nimport type { PricingConfig } from './types.js';\nimport { PricingError } from './types.js';\n\n/**\n * Service for calculating event storage prices with kind-based overrides.\n */\nexport class PricingService {\n private readonly basePricePerByte: bigint;\n private readonly kindOverrides: Map<number, bigint>;\n\n constructor(config: PricingConfig) {\n // Validate basePricePerByte is non-negative\n if (config.basePricePerByte < 0n) {\n throw new PricingError(\n 'basePricePerByte must be non-negative',\n 'INVALID_CONFIG'\n );\n }\n\n // Validate all kindOverrides are non-negative\n if (config.kindOverrides) {\n for (const [kind, price] of config.kindOverrides.entries()) {\n if (price < 0n) {\n throw new PricingError(\n `kindOverride for kind ${kind} must be non-negative`,\n 'INVALID_CONFIG'\n );\n }\n }\n }\n\n this.basePricePerByte = config.basePricePerByte;\n this.kindOverrides = config.kindOverrides ?? new Map();\n }\n\n /**\n * Calculate price for a Nostr event.\n *\n * @param event - The Nostr event to price\n * @returns The calculated price as bigint\n */\n calculatePrice(event: NostrEvent): bigint {\n const toonBytes = encodeEventToToon(event);\n return this.calculatePriceFromBytes(toonBytes, event.kind);\n }\n\n /**\n * Calculate price from raw TOON bytes and event kind.\n *\n * @param bytes - The TOON-encoded event bytes\n * @param kind - The event kind number\n * @returns The calculated price as bigint\n */\n calculatePriceFromBytes(bytes: Uint8Array, kind: number): bigint {\n const pricePerByte = this.getPricePerByte(kind);\n return BigInt(bytes.length) * pricePerByte;\n }\n\n /**\n * Get the effective price per byte for a given kind.\n *\n * @param kind - The event kind number\n * @returns The price per byte (kind override if exists, otherwise base price)\n */\n getPricePerByte(kind: number): bigint {\n return this.kindOverrides.get(kind) ?? this.basePricePerByte;\n }\n}\n","import { readFileSync } from 'fs';\nimport type { PricingConfig } from './types.js';\nimport { PricingError } from './types.js';\n\n/**\n * Load pricing configuration from environment variables.\n *\n * Environment variables:\n * - RELAY_BASE_PRICE_PER_BYTE: Base price per byte (default: \"10\")\n * - RELAY_KIND_OVERRIDES: JSON object mapping kind to price (optional)\n * Format: {\"1\":\"5\",\"30023\":\"100\"}\n *\n * @returns PricingConfig loaded from environment\n * @throws PricingError if env vars contain invalid values\n */\nexport function loadPricingConfigFromEnv(): PricingConfig {\n // Parse base price\n const basePriceStr = process.env['RELAY_BASE_PRICE_PER_BYTE'] ?? '10';\n let basePricePerByte: bigint;\n try {\n basePricePerByte = BigInt(basePriceStr);\n } catch {\n throw new PricingError(\n `Invalid RELAY_BASE_PRICE_PER_BYTE: \"${basePriceStr}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (basePricePerByte < 0n) {\n throw new PricingError(\n `Invalid RELAY_BASE_PRICE_PER_BYTE: value must be non-negative`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n // Parse kind overrides if present\n const kindOverridesStr = process.env['RELAY_KIND_OVERRIDES'];\n let kindOverrides: Map<number, bigint> | undefined;\n\n if (kindOverridesStr) {\n kindOverrides = parseKindOverridesJson(kindOverridesStr);\n }\n\n return {\n basePricePerByte,\n kindOverrides,\n };\n}\n\n/**\n * Load pricing configuration from a JSON file.\n *\n * File format:\n * {\n * \"basePricePerByte\": \"10\",\n * \"kindOverrides\": {\n * \"0\": \"0\",\n * \"1\": \"5\",\n * \"30023\": \"100\"\n * }\n * }\n *\n * @param path - Path to the JSON config file\n * @returns PricingConfig loaded from file\n * @throws PricingError if file cannot be read or contains invalid values\n */\nexport function loadPricingConfigFromFile(path: string): PricingConfig {\n let fileContent: string;\n try {\n fileContent = readFileSync(path, 'utf-8');\n } catch (error) {\n throw new PricingError(\n `Failed to read config file: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'CONFIG_FILE_ERROR'\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fileContent);\n } catch {\n throw new PricingError(\n `Invalid JSON in config file: ${path}`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new PricingError(\n `Config file must contain a JSON object`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n const config = parsed as Record<string, unknown>;\n\n // Parse base price\n const basePriceStr = config['basePricePerByte'];\n if (typeof basePriceStr !== 'string' && typeof basePriceStr !== 'number') {\n throw new PricingError(\n `Config file must contain \"basePricePerByte\" as string or number`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n let basePricePerByte: bigint;\n try {\n basePricePerByte = BigInt(basePriceStr);\n } catch {\n throw new PricingError(\n `Invalid basePricePerByte: \"${basePriceStr}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (basePricePerByte < 0n) {\n throw new PricingError(\n `basePricePerByte must be non-negative`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n // Parse kind overrides if present\n let kindOverrides: Map<number, bigint> | undefined;\n const kindOverridesValue = config['kindOverrides'];\n if (kindOverridesValue !== undefined) {\n if (typeof kindOverridesValue !== 'object' || kindOverridesValue === null) {\n throw new PricingError(\n `kindOverrides must be an object`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n kindOverrides = new Map();\n const overridesObj = kindOverridesValue as Record<string, unknown>;\n\n for (const [kindStr, priceValue] of Object.entries(overridesObj)) {\n const kind = parseInt(kindStr, 10);\n if (isNaN(kind)) {\n throw new PricingError(\n `Invalid kind in kindOverrides: \"${kindStr}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (typeof priceValue !== 'string' && typeof priceValue !== 'number') {\n throw new PricingError(\n `Invalid price for kind ${kind}: must be string or number`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n let price: bigint;\n try {\n price = BigInt(priceValue);\n } catch {\n throw new PricingError(\n `Invalid price for kind ${kind}: \"${priceValue}\" is not a valid integer`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n if (price < 0n) {\n throw new PricingError(\n `Price for kind ${kind} must be non-negative`,\n 'INVALID_FILE_CONFIG'\n );\n }\n\n kindOverrides.set(kind, price);\n }\n }\n\n return {\n basePricePerByte,\n kindOverrides,\n };\n}\n\n/**\n * Parse kind overrides JSON string to Map.\n */\nfunction parseKindOverridesJson(jsonStr: string): Map<number, bigint> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonStr);\n } catch {\n throw new PricingError(\n `Invalid JSON in RELAY_KIND_OVERRIDES: ${jsonStr}`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new PricingError(\n `RELAY_KIND_OVERRIDES must be a JSON object`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n const result = new Map<number, bigint>();\n const obj = parsed as Record<string, unknown>;\n\n for (const [kindStr, priceValue] of Object.entries(obj)) {\n const kind = parseInt(kindStr, 10);\n if (isNaN(kind)) {\n throw new PricingError(\n `Invalid kind in RELAY_KIND_OVERRIDES: \"${kindStr}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (typeof priceValue !== 'string' && typeof priceValue !== 'number') {\n throw new PricingError(\n `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: must be string or number`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n let price: bigint;\n try {\n price = BigInt(priceValue);\n } catch {\n throw new PricingError(\n `Invalid price for kind ${kind} in RELAY_KIND_OVERRIDES: \"${priceValue}\" is not a valid integer`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n if (price < 0n) {\n throw new PricingError(\n `Price for kind ${kind} in RELAY_KIND_OVERRIDES must be non-negative`,\n 'INVALID_ENV_CONFIG'\n );\n }\n\n result.set(kind, price);\n }\n\n return result;\n}\n","/**\n * Subscribe to upstream relays and propagate events into the local EventStore.\n *\n * Follows the same lifecycle pattern as core's discovery tracker and SocialPeerDiscovery:\n * - Accept optional SimplePool for testability\n * - start() returns { unsubscribe } cleanup handle\n * - isUnsubscribed guard prevents processing after teardown\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { Filter } from 'nostr-tools/filter';\nimport type { EventStore } from '../storage/index.js';\n\n/**\n * Configuration for RelaySubscriber.\n */\nexport interface RelaySubscriberConfig {\n /** Upstream relay URLs to subscribe to */\n relayUrls: string[];\n /** Nostr filter for which events to pull (e.g. kinds, authors) */\n filter: Filter;\n /** Verify event signatures before storing (default: true) */\n verifySignatures?: boolean;\n}\n\n/**\n * Subscribes to upstream Nostr relays and stores received events\n * in the local EventStore. Useful for relay-to-relay event propagation.\n */\nexport class RelaySubscriber {\n private readonly config: RelaySubscriberConfig;\n private readonly eventStore: EventStore;\n private readonly pool: SimplePool;\n private started = false;\n\n /**\n * @param config - Subscriber configuration\n * @param eventStore - Storage backend to write events into\n * @param pool - Optional SimplePool instance (creates new one if not provided)\n */\n constructor(\n config: RelaySubscriberConfig,\n eventStore: EventStore,\n pool?: SimplePool\n ) {\n this.config = config;\n this.eventStore = eventStore;\n this.pool = pool ?? new SimplePool();\n }\n\n /**\n * Start subscribing to the configured upstream relays.\n *\n * @returns Handle with unsubscribe() to stop the subscription\n * @throws Error if already started\n */\n start(): { unsubscribe: () => void } {\n if (this.started) {\n throw new Error('RelaySubscriber already started');\n }\n this.started = true;\n\n const shouldVerify = this.config.verifySignatures !== false;\n let isUnsubscribed = false;\n\n const subCloser = this.pool.subscribeMany(\n this.config.relayUrls,\n this.config.filter,\n {\n onevent: (event: NostrEvent) => {\n if (isUnsubscribed) return;\n\n if (shouldVerify && !verifyEvent(event)) {\n return;\n }\n\n try {\n this.eventStore.store(event);\n } catch (error) {\n console.warn(\n '[RelaySubscriber] Failed to store event:',\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n },\n }\n );\n\n return {\n unsubscribe: () => {\n if (!isUnsubscribed) {\n isUnsubscribed = true;\n subCloser.close();\n this.started = false;\n }\n },\n };\n }\n}\n","/**\n * @toon-protocol/relay\n *\n * ILP-gated Nostr relay with Business Logic Server.\n */\n\nexport const VERSION = '0.1.0';\n\n// Types\nexport type { RelayConfig } from './types.js';\nexport { DEFAULT_RELAY_CONFIG } from './types.js';\n\n// Storage\nexport type { EventStore } from './storage/index.js';\nexport {\n InMemoryEventStore,\n SqliteEventStore,\n RelayError,\n} from './storage/index.js';\n\n// Filters\nexport { matchFilter } from './filters/index.js';\n\n// WebSocket\nexport type { Subscription } from './websocket/index.js';\nexport { ConnectionHandler, NostrRelayServer } from './websocket/index.js';\n\n// TOON encoding/decoding\nexport {\n encodeEventToToon,\n decodeEventFromToon,\n ToonEncodeError,\n ToonDecodeError,\n} from './toon/index.js';\n\n// Business Logic Server\nexport type {\n BlsConfig,\n HandlePacketRequest,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n HandlePacketResponse,\n} from './bls/index.js';\nexport {\n BlsError,\n ILP_ERROR_CODES,\n BusinessLogicServer,\n generateFulfillment,\n isValidPubkey,\n} from './bls/index.js';\n\n// Pricing\nexport type { PricingConfig } from './pricing/index.js';\nexport {\n PricingError,\n PricingService,\n loadPricingConfigFromEnv,\n loadPricingConfigFromFile,\n} from './pricing/index.js';\n\n// Subscriber\nexport type { RelaySubscriberConfig } from './subscriber/index.js';\nexport { RelaySubscriber } from './subscriber/index.js';\n\n// Re-exports from @toon-protocol/bls removed to avoid circular dependency\n// Downstream consumers should import directly from @toon-protocol/bls instead\n"],"mappings":"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","names":["verifyEvent"]}
|