@toon-protocol/relay 2.0.2 → 2.1.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/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/launcher/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI entrypoint for @toon-protocol/relay.\n *\n * Thin wrapper around startRelay() that parses CLI flags and environment\n * variables, then delegates to relay.ts. The relay is a plain read/write app:\n * free NIP-01 WebSocket reads plus an HTTP `POST /write` surface. Payment is\n * enforced upstream by an external terminator, so there are no connector,\n * ILP, chain, or pricing options here.\n *\n * Usage:\n * relay --secret-key <hex>\n * NOSTR_SECRET_KEY=<hex> relay\n *\n * Environment variables override defaults; CLI flags override environment variables.\n */\n\nimport { parseArgs } from 'node:util';\nimport { startRelay } from './relay.js';\nimport type { RelayConfig, RelayInstance } from './relay.js';\n\n// ---------- CLI Parsing ----------\n\nfunction printHelp(): void {\n console.log(\n `\nUsage: relay [options]\n\nOptions:\n --mnemonic <words> BIP-39 mnemonic (12 or 24 words; NIP-06 derivation)\n --secret-key <hex> 32-byte secret key in hex\n --relay-port <port> WebSocket read port (default: 7100)\n --bls-port <port> HTTP write/health port (default: 3100)\n --host <host> WebSocket bind host (default: 0.0.0.0)\n --write-host <host> HTTP write/health bind host (default: 0.0.0.0).\n The write port must only be reachable via the\n payment-gating connector; bind it to a loopback/\n internal address when not isolated by docker\n networking (relay#85 exposure guard)\n --data-dir <path> Data directory for the SQLite store (default: ./data)\n --dev-mode Skip event-signature verification on POST /write\n --verify-ephemeral Run FULL schnorr verification on ephemeral kinds\n (20000 <= kind < 30000). Default OFF: the write\n path is payment-gated and clients verify\n signatures themselves, so ephemeral kinds skip\n schnorr and keep only the SHA-256 id check\n (relay#85). Set this when the write port is\n fronted by anything other than a payment-gating\n connector\n --verify-workers <n> Worker threads for persistent-kind signature\n verification (default: CPU count - 1, minimum 0).\n 0 = verify inline on the event loop (automatic on\n 1-core boxes; the escape hatch back to the\n pre-pool behavior, relay#85)\n --max-connections <n> Maximum concurrent WebSocket read connections\n (default: 4096; each costs one file descriptor --\n mind ulimit -n, relay#90)\n --log-writes Log one line per accepted POST /write (debug; off\n by default -- per-event logging is write-path\n tail jitter, relay#85)\n --help Show this help message\n\nEnvironment Variables:\n TOON_MNEMONIC Same as --mnemonic\n TOON_SECRET_KEY Same as --secret-key\n NOSTR_SECRET_KEY Alias for TOON_SECRET_KEY (identity); TOON_SECRET_KEY wins\n TOON_RELAY_PORT Same as --relay-port\n TOON_BLS_PORT Same as --bls-port\n TOON_HOST Same as --host\n TOON_WRITE_HOST Same as --write-host\n TOON_DATA_DIR Same as --data-dir\n TOON_DEV_MODE Same as --dev-mode (set to \"true\")\n TOON_VERIFY_EPHEMERAL Same as --verify-ephemeral (set to \"true\")\n TOON_VERIFY_WORKERS Same as --verify-workers\n TOON_MAX_CONNECTIONS Same as --max-connections\n TOON_LOG_WRITES Same as --log-writes (set to \"true\")\n\nSecurity:\n Prefer TOON_MNEMONIC / TOON_SECRET_KEY / NOSTR_SECRET_KEY environment\n variables over --mnemonic / --secret-key CLI flags. CLI arguments are visible\n to other users on the system via process listings (e.g. ps aux). See CWE-214.\n`.trim()\n );\n}\n\nfunction parseCli(): RelayConfig {\n const { values } = parseArgs({\n options: {\n mnemonic: { type: 'string' },\n 'secret-key': { type: 'string' },\n 'relay-port': { type: 'string' },\n 'bls-port': { type: 'string' },\n host: { type: 'string' },\n 'write-host': { type: 'string' },\n 'data-dir': { type: 'string' },\n 'dev-mode': { type: 'boolean' },\n 'verify-ephemeral': { type: 'boolean' },\n 'verify-workers': { type: 'string' },\n 'max-connections': { type: 'string' },\n 'log-writes': { type: 'boolean' },\n help: { type: 'boolean' },\n },\n strict: true,\n allowPositionals: false,\n });\n\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n\n // Warn about process-listing exposure (CWE-214) when secrets are passed via CLI flags\n if (values.mnemonic) {\n console.warn(\n 'Warning: --mnemonic is visible in process listings. ' +\n 'Prefer TOON_MNEMONIC environment variable for production use.'\n );\n }\n if (values['secret-key']) {\n console.warn(\n 'Warning: --secret-key is visible in process listings. ' +\n 'Prefer TOON_SECRET_KEY environment variable for production use.'\n );\n }\n\n const mnemonic = values.mnemonic ?? process.env['TOON_MNEMONIC'] ?? undefined;\n\n // Identity secret key. NOSTR_SECRET_KEY is accepted as an alias for\n // TOON_SECRET_KEY so the container honors the same identity env the connector\n // compose uses. TOON_SECRET_KEY wins when both are set.\n const secretKeyHex =\n values['secret-key'] ??\n process.env['TOON_SECRET_KEY'] ??\n process.env['NOSTR_SECRET_KEY'] ??\n undefined;\n\n let secretKey: Uint8Array | undefined;\n if (secretKeyHex) {\n if (secretKeyHex.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(secretKeyHex)) {\n console.error('Error: --secret-key must be a 64-character hex string');\n process.exit(1);\n }\n secretKey = Uint8Array.from(Buffer.from(secretKeyHex, 'hex'));\n }\n\n if (!mnemonic && !secretKey) {\n console.error(\n 'Error: one of --mnemonic (or TOON_MNEMONIC) or --secret-key ' +\n '(or TOON_SECRET_KEY / NOSTR_SECRET_KEY) is required'\n );\n process.exit(1);\n }\n if (mnemonic && secretKey) {\n console.error('Error: provide either a mnemonic or a secret key, not both');\n process.exit(1);\n }\n\n const relayPortStr =\n values['relay-port'] ?? process.env['TOON_RELAY_PORT'] ?? undefined;\n const relayPort = relayPortStr ? parseInt(relayPortStr, 10) : undefined;\n if (\n relayPort !== undefined &&\n (Number.isNaN(relayPort) || relayPort <= 0 || relayPort > 65535)\n ) {\n console.error('Error: --relay-port must be an integer between 1 and 65535');\n process.exit(1);\n }\n\n const blsPortStr =\n values['bls-port'] ?? process.env['TOON_BLS_PORT'] ?? undefined;\n const blsPort = blsPortStr ? parseInt(blsPortStr, 10) : undefined;\n if (\n blsPort !== undefined &&\n (Number.isNaN(blsPort) || blsPort <= 0 || blsPort > 65535)\n ) {\n console.error('Error: --bls-port must be an integer between 1 and 65535');\n process.exit(1);\n }\n\n const host = values.host ?? process.env['TOON_HOST'] ?? undefined;\n\n const writeHost =\n values['write-host'] ?? process.env['TOON_WRITE_HOST'] ?? undefined;\n\n const dataDir =\n values['data-dir'] ?? process.env['TOON_DATA_DIR'] ?? undefined;\n\n const devMode =\n values['dev-mode'] ??\n (process.env['TOON_DEV_MODE'] === 'true' ? true : undefined);\n\n // Paid-ephemeral verify skip (relay#85): full verification on ephemeral\n // kinds is opt-in (the skip is the default; see RelayConfig.verifyEphemeral\n // for the payment-gated invariant that makes that safe).\n const verifyEphemeral =\n values['verify-ephemeral'] ??\n (process.env['TOON_VERIFY_EPHEMERAL'] === 'true' ? true : undefined);\n\n // Verify-pool size (relay#85). 0 is a meaningful value (inline path), so\n // only an absent/blank setting falls through to the default.\n const verifyWorkersStr =\n values['verify-workers'] ?? process.env['TOON_VERIFY_WORKERS'] ?? undefined;\n const verifyWorkers = verifyWorkersStr\n ? parseInt(verifyWorkersStr, 10)\n : undefined;\n if (\n verifyWorkers !== undefined &&\n (Number.isNaN(verifyWorkers) || verifyWorkers < 0 || verifyWorkers > 256)\n ) {\n console.error('Error: --verify-workers must be an integer >= 0');\n process.exit(1);\n }\n\n // WS connection cap (relay#90). Each connection costs one fd; the server\n // logs an advisory warning at startup if the cap exceeds the soft limit.\n const maxConnectionsStr =\n values['max-connections'] ??\n process.env['TOON_MAX_CONNECTIONS'] ??\n undefined;\n const maxConnections = maxConnectionsStr\n ? parseInt(maxConnectionsStr, 10)\n : undefined;\n if (\n maxConnections !== undefined &&\n (Number.isNaN(maxConnections) || maxConnections <= 0)\n ) {\n console.error('Error: --max-connections must be a positive integer');\n process.exit(1);\n }\n\n const logWrites =\n values['log-writes'] ??\n (process.env['TOON_LOG_WRITES'] === 'true' ? true : undefined);\n\n const config: RelayConfig = {\n ...(mnemonic && { mnemonic }),\n ...(secretKey && { secretKey }),\n ...(relayPort !== undefined && { relayPort }),\n ...(blsPort !== undefined && { blsPort }),\n ...(host && { host }),\n ...(writeHost && { writeHost }),\n ...(dataDir && { dataDir }),\n ...(devMode !== undefined && { devMode }),\n ...(verifyEphemeral !== undefined && { verifyEphemeral }),\n ...(verifyWorkers !== undefined && { verifyWorkers }),\n ...(maxConnections !== undefined && { maxConnections }),\n ...(logWrites !== undefined && { logWrites }),\n };\n\n return config;\n}\n\n// ---------- Main ----------\n\nasync function main(): Promise<void> {\n const config = parseCli();\n\n console.log('\\n' + '='.repeat(50));\n console.log('TOON Relay Starting');\n console.log('='.repeat(50) + '\\n');\n\n const instance: RelayInstance = await startRelay(config);\n\n console.log('\\n' + '='.repeat(50));\n console.log('TOON Relay Ready');\n console.log('='.repeat(50));\n console.log(` Pubkey: ${instance.pubkey}`);\n console.log(` Reads: ws://localhost:${instance.config.relayPort}`);\n console.log(` Writes: http://localhost:${instance.config.blsPort}/write`);\n console.log(` Health: http://localhost:${instance.config.blsPort}/health`);\n console.log('='.repeat(50) + '\\n');\n\n // Wire graceful shutdown\n const shutdown = async (signal: string): Promise<void> => {\n console.log(`\\n[Shutdown] Received ${signal}`);\n await instance.stop();\n console.log('[Shutdown] Complete');\n process.exit(0);\n };\n\n process.on('SIGINT', () => {\n shutdown('SIGINT').catch(console.error);\n });\n process.on('SIGTERM', () => {\n shutdown('SIGTERM').catch(console.error);\n });\n}\n\nmain().catch((error: unknown) => {\n console.error('[Fatal] Startup error:', error);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/launcher/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI entrypoint for @toon-protocol/relay.\n *\n * Thin wrapper around startRelay() that parses CLI flags and environment\n * variables, then delegates to relay.ts. The relay is a plain read/write app:\n * free NIP-01 WebSocket reads plus an HTTP `POST /write` surface. Payment is\n * enforced upstream by an external terminator, so there are no connector,\n * ILP, chain, or pricing options here.\n *\n * Usage:\n * relay --secret-key <hex>\n * NOSTR_SECRET_KEY=<hex> relay\n *\n * Environment variables override defaults; CLI flags override environment variables.\n */\n\nimport { parseArgs } from 'node:util';\nimport { startRelay } from './relay.js';\nimport type { RelayConfig, RelayInstance } from './relay.js';\nimport { DEFAULT_EPHEMERAL_RATE_LIMIT } from './handlers/write-ephemeral-handler.js';\nimport { parseBlockedEventIds } from '../nips/blocklist.js';\n\n// ---------- CLI Parsing ----------\n\nfunction printHelp(): void {\n console.log(\n `\nUsage: relay [options]\n\nOptions:\n --mnemonic <words> BIP-39 mnemonic (12 or 24 words; NIP-06 derivation)\n --secret-key <hex> 32-byte secret key in hex\n --relay-port <port> WebSocket read port (default: 7100)\n --bls-port <port> HTTP write/health port (default: 3100)\n --host <host> WebSocket bind host (default: 0.0.0.0)\n --write-host <host> HTTP write/health bind host (default: 0.0.0.0).\n The write port must only be reachable via the\n payment-gating connector; bind it to a loopback/\n internal address when not isolated by docker\n networking (relay#85 exposure guard)\n --data-dir <path> Data directory for the SQLite store (default: ./data)\n --dev-mode Skip event-signature verification on POST /write\n --verify-ephemeral Run FULL schnorr verification on ephemeral kinds\n (20000 <= kind < 30000). Default OFF: the write\n path is payment-gated and clients verify\n signatures themselves, so ephemeral kinds skip\n schnorr and keep only the SHA-256 id check\n (relay#85). Set this when the write port is\n fronted by anything other than a payment-gating\n connector\n --verify-workers <n> Worker threads for persistent-kind signature\n verification (default: CPU count - 1, minimum 0).\n 0 = verify inline on the event loop (automatic on\n 1-core boxes; the escape hatch back to the\n pre-pool behavior, relay#85)\n --max-connections <n> Maximum concurrent WebSocket read connections\n (default: 4096; each costs one file descriptor --\n mind ulimit -n, relay#90)\n --ephemeral-rate-limit <n>\n Free ephemeral write lane (POST /write-ephemeral,\n relay#129): max requests per key per window\n (default: 200). This lane has no payment gate, so\n this bound IS its admission control\n --ephemeral-rate-window-ms <n>\n Free ephemeral write lane: rate-limit window in\n milliseconds (default: 10000)\n --ephemeral-max-body-bytes <n>\n Free ephemeral write lane: request body size cap\n in bytes (default: 8192)\n --log-writes Log one line per accepted POST /write (debug; off\n by default -- per-event logging is write-path\n tail jitter, relay#85)\n --no-enforce-expiration Serve events past their NIP-40 expiration tag.\n KILL SWITCH back to the pre-relay#137 behaviour;\n enforcement is ON by default. Also disables the\n reaper -- a relay still serving expired events\n must not be silently deleting them\n --expiration-reap-grace-seconds <n>\n How long an expired event stays on disk before the\n reaper deletes it (default: 86400). The window in\n which flipping enforcement back off is a real\n recovery rather than an apology. 0 = reap at once\n --expiration-reap-interval-seconds <n>\n Reaper sweep interval (default: 3600). 0 disables\n reaping; serve-time filtering is unaffected\n --blocked-event-ids <ids>\n Comma-separated 64-hex event ids this relay\n refuses to store or serve. The escape hatch for an\n event whose author key is gone, so neither NIP-01\n replacement nor NIP-09 deletion can reach it. Ids\n only, never pubkeys; every id is logged at startup\n --help Show this help message\n\nEnvironment Variables:\n TOON_MNEMONIC Same as --mnemonic\n TOON_SECRET_KEY Same as --secret-key\n NOSTR_SECRET_KEY Alias for TOON_SECRET_KEY (identity); TOON_SECRET_KEY wins\n TOON_RELAY_PORT Same as --relay-port\n TOON_BLS_PORT Same as --bls-port\n TOON_HOST Same as --host\n TOON_WRITE_HOST Same as --write-host\n TOON_DATA_DIR Same as --data-dir\n TOON_DEV_MODE Same as --dev-mode (set to \"true\")\n TOON_VERIFY_EPHEMERAL Same as --verify-ephemeral (set to \"true\")\n TOON_VERIFY_WORKERS Same as --verify-workers\n TOON_MAX_CONNECTIONS Same as --max-connections\n TOON_EPHEMERAL_RATE_LIMIT Same as --ephemeral-rate-limit\n TOON_EPHEMERAL_RATE_WINDOW_MS Same as --ephemeral-rate-window-ms\n TOON_EPHEMERAL_MAX_BODY_BYTES Same as --ephemeral-max-body-bytes\n TOON_LOG_WRITES Same as --log-writes (set to \"true\")\n TOON_ENFORCE_EXPIRATION Set to \"false\" for --no-enforce-expiration\n TOON_EXPIRATION_REAP_GRACE_SECONDS Same as --expiration-reap-grace-seconds\n TOON_EXPIRATION_REAP_INTERVAL_SECONDS Same as --expiration-reap-interval-seconds\n TOON_BLOCKED_EVENT_IDS Same as --blocked-event-ids\n\nSecurity:\n Prefer TOON_MNEMONIC / TOON_SECRET_KEY / NOSTR_SECRET_KEY environment\n variables over --mnemonic / --secret-key CLI flags. CLI arguments are visible\n to other users on the system via process listings (e.g. ps aux). See CWE-214.\n`.trim()\n );\n}\n\n/**\n * Parse an optional numeric option that must be a positive integer.\n *\n * @param flag - The flag name, used verbatim in the error message.\n * @param raw - The flag value, or its env-var fallback, or undefined.\n * @returns The parsed value, or undefined when neither source is set (so the\n * caller leaves the field off `RelayConfig` and `startRelay()` applies its\n * own default). Exits the process on a non-positive/non-numeric value.\n */\nfunction parsePositiveIntOption(\n flag: string,\n raw: string | undefined\n): number | undefined {\n if (!raw) {\n return undefined;\n }\n const parsed = parseInt(raw, 10);\n if (Number.isNaN(parsed) || parsed <= 0) {\n console.error(`Error: --${flag} must be a positive integer`);\n process.exit(1);\n }\n return parsed;\n}\n\n/**\n * Parse an optional numeric option that must be a non-negative integer.\n *\n * Separate from `parsePositiveIntOption` because 0 is MEANINGFUL for the\n * retention knobs: a 0 reap grace means \"delete the moment it expires\" and a\n * 0 reap interval means \"never sweep\", both deliberate operator choices.\n *\n * @param flag - The flag name, used verbatim in the error message.\n * @param raw - The flag value, or its env-var fallback, or undefined.\n * @returns The parsed value, or undefined when neither source is set.\n */\nfunction parseNonNegativeIntOption(\n flag: string,\n raw: string | undefined\n): number | undefined {\n if (raw === undefined || raw === '') {\n return undefined;\n }\n const parsed = parseInt(raw, 10);\n if (Number.isNaN(parsed) || parsed < 0) {\n console.error(`Error: --${flag} must be an integer >= 0`);\n process.exit(1);\n }\n return parsed;\n}\n\nfunction parseCli(): RelayConfig {\n const { values } = parseArgs({\n options: {\n mnemonic: { type: 'string' },\n 'secret-key': { type: 'string' },\n 'relay-port': { type: 'string' },\n 'bls-port': { type: 'string' },\n host: { type: 'string' },\n 'write-host': { type: 'string' },\n 'data-dir': { type: 'string' },\n 'dev-mode': { type: 'boolean' },\n 'verify-ephemeral': { type: 'boolean' },\n 'verify-workers': { type: 'string' },\n 'max-connections': { type: 'string' },\n 'ephemeral-rate-limit': { type: 'string' },\n 'ephemeral-rate-window-ms': { type: 'string' },\n 'ephemeral-max-body-bytes': { type: 'string' },\n 'log-writes': { type: 'boolean' },\n 'no-enforce-expiration': { type: 'boolean' },\n 'expiration-reap-grace-seconds': { type: 'string' },\n 'expiration-reap-interval-seconds': { type: 'string' },\n 'blocked-event-ids': { type: 'string' },\n help: { type: 'boolean' },\n },\n strict: true,\n allowPositionals: false,\n });\n\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n\n // Warn about process-listing exposure (CWE-214) when secrets are passed via CLI flags\n if (values.mnemonic) {\n console.warn(\n 'Warning: --mnemonic is visible in process listings. ' +\n 'Prefer TOON_MNEMONIC environment variable for production use.'\n );\n }\n if (values['secret-key']) {\n console.warn(\n 'Warning: --secret-key is visible in process listings. ' +\n 'Prefer TOON_SECRET_KEY environment variable for production use.'\n );\n }\n\n const mnemonic = values.mnemonic ?? process.env['TOON_MNEMONIC'] ?? undefined;\n\n // Identity secret key. NOSTR_SECRET_KEY is accepted as an alias for\n // TOON_SECRET_KEY so the container honors the same identity env the connector\n // compose uses. TOON_SECRET_KEY wins when both are set.\n const secretKeyHex =\n values['secret-key'] ??\n process.env['TOON_SECRET_KEY'] ??\n process.env['NOSTR_SECRET_KEY'] ??\n undefined;\n\n let secretKey: Uint8Array | undefined;\n if (secretKeyHex) {\n if (secretKeyHex.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(secretKeyHex)) {\n console.error('Error: --secret-key must be a 64-character hex string');\n process.exit(1);\n }\n secretKey = Uint8Array.from(Buffer.from(secretKeyHex, 'hex'));\n }\n\n if (!mnemonic && !secretKey) {\n console.error(\n 'Error: one of --mnemonic (or TOON_MNEMONIC) or --secret-key ' +\n '(or TOON_SECRET_KEY / NOSTR_SECRET_KEY) is required'\n );\n process.exit(1);\n }\n if (mnemonic && secretKey) {\n console.error('Error: provide either a mnemonic or a secret key, not both');\n process.exit(1);\n }\n\n const relayPortStr =\n values['relay-port'] ?? process.env['TOON_RELAY_PORT'] ?? undefined;\n const relayPort = relayPortStr ? parseInt(relayPortStr, 10) : undefined;\n if (\n relayPort !== undefined &&\n (Number.isNaN(relayPort) || relayPort <= 0 || relayPort > 65535)\n ) {\n console.error('Error: --relay-port must be an integer between 1 and 65535');\n process.exit(1);\n }\n\n const blsPortStr =\n values['bls-port'] ?? process.env['TOON_BLS_PORT'] ?? undefined;\n const blsPort = blsPortStr ? parseInt(blsPortStr, 10) : undefined;\n if (\n blsPort !== undefined &&\n (Number.isNaN(blsPort) || blsPort <= 0 || blsPort > 65535)\n ) {\n console.error('Error: --bls-port must be an integer between 1 and 65535');\n process.exit(1);\n }\n\n const host = values.host ?? process.env['TOON_HOST'] ?? undefined;\n\n const writeHost =\n values['write-host'] ?? process.env['TOON_WRITE_HOST'] ?? undefined;\n\n const dataDir =\n values['data-dir'] ?? process.env['TOON_DATA_DIR'] ?? undefined;\n\n const devMode =\n values['dev-mode'] ??\n (process.env['TOON_DEV_MODE'] === 'true' ? true : undefined);\n\n // Paid-ephemeral verify skip (relay#85): full verification on ephemeral\n // kinds is opt-in (the skip is the default; see RelayConfig.verifyEphemeral\n // for the payment-gated invariant that makes that safe).\n const verifyEphemeral =\n values['verify-ephemeral'] ??\n (process.env['TOON_VERIFY_EPHEMERAL'] === 'true' ? true : undefined);\n\n // Verify-pool size (relay#85). 0 is a meaningful value (inline path), so\n // only an absent/blank setting falls through to the default.\n const verifyWorkersStr =\n values['verify-workers'] ?? process.env['TOON_VERIFY_WORKERS'] ?? undefined;\n const verifyWorkers = verifyWorkersStr\n ? parseInt(verifyWorkersStr, 10)\n : undefined;\n if (\n verifyWorkers !== undefined &&\n (Number.isNaN(verifyWorkers) || verifyWorkers < 0 || verifyWorkers > 256)\n ) {\n console.error('Error: --verify-workers must be an integer >= 0');\n process.exit(1);\n }\n\n // WS connection cap (relay#90). Each connection costs one fd; the server\n // logs an advisory warning at startup if the cap exceeds the soft limit.\n const maxConnectionsStr =\n values['max-connections'] ??\n process.env['TOON_MAX_CONNECTIONS'] ??\n undefined;\n const maxConnections = maxConnectionsStr\n ? parseInt(maxConnectionsStr, 10)\n : undefined;\n if (\n maxConnections !== undefined &&\n (Number.isNaN(maxConnections) || maxConnections <= 0)\n ) {\n console.error('Error: --max-connections must be a positive integer');\n process.exit(1);\n }\n\n // Free ephemeral write lane bounds (relay#129) -- POST /write-ephemeral has\n // no payment gate, so these ARE its admission control. All three share the\n // same shape: unset falls through to startRelay()'s own default, anything\n // that is not a positive integer is a startup error.\n const ephemeralRateLimitMax = parsePositiveIntOption(\n 'ephemeral-rate-limit',\n values['ephemeral-rate-limit'] ?? process.env['TOON_EPHEMERAL_RATE_LIMIT']\n );\n const ephemeralRateWindowMs = parsePositiveIntOption(\n 'ephemeral-rate-window-ms',\n values['ephemeral-rate-window-ms'] ??\n process.env['TOON_EPHEMERAL_RATE_WINDOW_MS']\n );\n const ephemeralMaxBodyBytes = parsePositiveIntOption(\n 'ephemeral-max-body-bytes',\n values['ephemeral-max-body-bytes'] ??\n process.env['TOON_EPHEMERAL_MAX_BODY_BYTES']\n );\n\n // maxRequests/windowMs travel together as one RelayConfig field -- only\n // build it once at least one half was actually provided, so an unset pair\n // still falls through to startRelay()'s own default.\n const ephemeralRateLimit =\n ephemeralRateLimitMax !== undefined || ephemeralRateWindowMs !== undefined\n ? {\n maxRequests:\n ephemeralRateLimitMax ?? DEFAULT_EPHEMERAL_RATE_LIMIT.maxRequests,\n windowMs:\n ephemeralRateWindowMs ?? DEFAULT_EPHEMERAL_RATE_LIMIT.windowMs,\n }\n : undefined;\n\n const logWrites =\n values['log-writes'] ??\n (process.env['TOON_LOG_WRITES'] === 'true' ? true : undefined);\n\n // --- Retention (relay#137) ---\n // Enforcement is ON by default, so the only thing worth expressing here is\n // turning it OFF. `TOON_ENFORCE_EXPIRATION` is compared against the exact\n // string 'false' so a typo ('False', 'no', '0') fails SAFE -- towards\n // enforcing -- rather than silently reopening the hole this closed.\n const enforceExpiration =\n values['no-enforce-expiration'] === true ||\n process.env['TOON_ENFORCE_EXPIRATION'] === 'false'\n ? false\n : undefined;\n\n const expirationReapGraceSeconds = parseNonNegativeIntOption(\n 'expiration-reap-grace-seconds',\n values['expiration-reap-grace-seconds'] ??\n process.env['TOON_EXPIRATION_REAP_GRACE_SECONDS']\n );\n const expirationReapIntervalSeconds = parseNonNegativeIntOption(\n 'expiration-reap-interval-seconds',\n values['expiration-reap-interval-seconds'] ??\n process.env['TOON_EXPIRATION_REAP_INTERVAL_SECONDS']\n );\n\n // Operator blocklist. A malformed id is a hard startup error, not a warning:\n // its failure mode is an operator who believes an event is blocked while the\n // relay keeps serving it.\n const blocklist = parseBlockedEventIds(\n values['blocked-event-ids'] ?? process.env['TOON_BLOCKED_EVENT_IDS']\n );\n if (blocklist.invalid.length > 0) {\n console.error(\n 'Error: --blocked-event-ids entries must be 64-character hex event ids; ' +\n `rejected: ${blocklist.invalid.join(', ')}`\n );\n process.exit(1);\n }\n const blockedEventIds = blocklist.ids.length > 0 ? blocklist.ids : undefined;\n\n const config: RelayConfig = {\n ...(mnemonic && { mnemonic }),\n ...(secretKey && { secretKey }),\n ...(relayPort !== undefined && { relayPort }),\n ...(blsPort !== undefined && { blsPort }),\n ...(host && { host }),\n ...(writeHost && { writeHost }),\n ...(dataDir && { dataDir }),\n ...(devMode !== undefined && { devMode }),\n ...(verifyEphemeral !== undefined && { verifyEphemeral }),\n ...(verifyWorkers !== undefined && { verifyWorkers }),\n ...(maxConnections !== undefined && { maxConnections }),\n ...(ephemeralRateLimit !== undefined && { ephemeralRateLimit }),\n ...(ephemeralMaxBodyBytes !== undefined && { ephemeralMaxBodyBytes }),\n ...(logWrites !== undefined && { logWrites }),\n ...(enforceExpiration !== undefined && { enforceExpiration }),\n ...(expirationReapGraceSeconds !== undefined && {\n expirationReapGraceSeconds,\n }),\n ...(expirationReapIntervalSeconds !== undefined && {\n expirationReapIntervalSeconds,\n }),\n ...(blockedEventIds !== undefined && { blockedEventIds }),\n };\n\n return config;\n}\n\n// ---------- Main ----------\n\nasync function main(): Promise<void> {\n const config = parseCli();\n\n console.log('\\n' + '='.repeat(50));\n console.log('TOON Relay Starting');\n console.log('='.repeat(50) + '\\n');\n\n const instance: RelayInstance = await startRelay(config);\n\n console.log('\\n' + '='.repeat(50));\n console.log('TOON Relay Ready');\n console.log('='.repeat(50));\n console.log(` Pubkey: ${instance.pubkey}`);\n console.log(` Reads: ws://localhost:${instance.config.relayPort}`);\n console.log(\n ` Writes: http://localhost:${instance.config.blsPort}/write`\n );\n console.log(\n ` Ephemeral: http://localhost:${instance.config.blsPort}/write-ephemeral (free, relay#129)`\n );\n console.log(\n ` Health: http://localhost:${instance.config.blsPort}/health`\n );\n console.log('='.repeat(50) + '\\n');\n\n // Wire graceful shutdown\n const shutdown = async (signal: string): Promise<void> => {\n console.log(`\\n[Shutdown] Received ${signal}`);\n await instance.stop();\n console.log('[Shutdown] Complete');\n process.exit(0);\n };\n\n process.on('SIGINT', () => {\n shutdown('SIGINT').catch(console.error);\n });\n process.on('SIGTERM', () => {\n shutdown('SIGTERM').catch(console.error);\n });\n}\n\nmain().catch((error: unknown) => {\n console.error('[Fatal] Startup error:', error);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}