import "dotenv/config"; function env(key: string, defaultValue?: string): string { const v = process.env[key]; if (v !== undefined) return v; if (defaultValue !== undefined) return defaultValue; throw new Error(`Missing required env: ${key}`); } function envInt(key: string, defaultValue: number): number { const v = process.env[key]; if (v === undefined) return defaultValue; const n = parseInt(v, 10); if (Number.isNaN(n)) throw new Error(`Invalid integer env: ${key}`); return n; } function envBool(key: string, defaultValue: boolean): boolean { const v = process.env[key]; if (v === undefined) return defaultValue; return v === "true" || v === "1"; } export const config = { port: envInt("PORT", 3001), trustProxy: envBool("TRUST_PROXY", false), /** Public path prefix when behind a reverse proxy that strips it (e.g. nginx /api/ -> backend /). */ publicBasePath: (process.env.PUBLIC_BASE_PATH ?? "").replace(/\/$/, ""), /** Host of the API subdomain (e.g. faucetapi.lnpulse.app). When request Host matches, OpenAPI server URL is /. */ apiHost: (process.env.API_HOST ?? "faucetapi.lnpulse.app").split(":")[0], allowedOrigins: (process.env.ALLOWED_ORIGINS ?? process.env.FRONTEND_URL ?? "http://localhost:5173,http://localhost:5174").split(",").map((s) => s.trim()), // Database: omit DATABASE_URL for SQLite; set for Postgres databaseUrl: process.env.DATABASE_URL as string | undefined, sqlitePath: process.env.SQLITE_PATH ?? "./data/faucet.db", // Security hmacIpSecret: env("HMAC_IP_SECRET"), jwtSecret: env("JWT_SECRET", process.env.HMAC_IP_SECRET ?? ""), jwtExpiresInSeconds: envInt("JWT_EXPIRES_IN_SECONDS", 86400 * 7), // 7 days nip98MaxSkewSeconds: envInt("NIP98_MAX_SKEW_SECONDS", 300), nonceTtlSeconds: envInt("NONCE_TTL_SECONDS", 600), // Faucet economics faucetEnabled: envBool("FAUCET_ENABLED", true), emergencyStop: envBool("EMERGENCY_STOP", false), faucetMinSats: envInt("FAUCET_MIN_SATS", 1), faucetMaxSats: envInt("FAUCET_MAX_SATS", 5), dailyBudgetSats: envInt("DAILY_BUDGET_SATS", 10000), maxClaimsPerDay: envInt("MAX_CLAIMS_PER_DAY", 100), minWalletBalanceSats: envInt("MIN_WALLET_BALANCE_SATS", 1000), // Eligibility minAccountAgeDays: envInt("MIN_ACCOUNT_AGE_DAYS", 14), minActivityScore: envInt("MIN_ACTIVITY_SCORE", 30), minNotesCount: envInt("MIN_NOTES_COUNT", 5), minFollowingCount: envInt("MIN_FOLLOWING_COUNT", 10), minFollowersCount: envInt("MIN_FOLLOWERS_COUNT", 0), activityLookbackDays: envInt("ACTIVITY_LOOKBACK_DAYS", 90), // Cooldowns cooldownDays: envInt("COOLDOWN_DAYS", 7), ipCooldownDays: envInt("IP_COOLDOWN_DAYS", 7), maxClaimsPerIpPerPeriod: envInt("MAX_CLAIMS_PER_IP_PER_PERIOD", 1), // Nostr (defaults include relays common for remote signers / NIP-05) nostrRelays: (process.env.NOSTR_RELAYS ?? "wss://relay.damus.io,wss://relay.nostr.band,wss://relay.getalby.com,wss://nos.lol").split(",").map((s) => s.trim()), relayTimeoutMs: envInt("RELAY_TIMEOUT_MS", 5000), maxEventsFetch: envInt("MAX_EVENTS_FETCH", 500), metadataCacheHours: envInt("METADATA_CACHE_HOURS", 24), // LNbits lnbitsBaseUrl: env("LNBITS_BASE_URL").replace(/\/$/, ""), lnbitsAdminKey: env("LNBITS_ADMIN_KEY"), lnbitsWalletId: env("LNBITS_WALLET_ID"), depositLightningAddress: process.env.DEPOSIT_LIGHTNING_ADDRESS ?? "", depositLnurlp: process.env.DEPOSIT_LNURLP ?? "", cashuRedeemApiUrl: (process.env.CASHU_REDEEM_API_URL ?? "https://cashu-redeem.azzamo.net").replace(/\/$/, ""), // Sponsors baseSponsorPricePerDay: envInt("BASE_SPONSOR_PRICE_PER_DAY", 200), sponsorMaxActivePerUser: envInt("SPONSOR_MAX_ACTIVE_PER_USER", 5), sponsorMaxVisible: envInt("SPONSOR_MAX_VISIBLE", 6), adminPubkeys: (process.env.ADMIN_PUBKEYS ?? "").split(",").map((s) => s.trim()).filter(Boolean), /** Public API base URL for LNbits webhook (e.g. https://api.example.com). Required for sponsor payments. */ publicApiUrl: (process.env.PUBLIC_API_URL ?? process.env.FRONTEND_URL ?? "").replace(/\/$/, ""), }; export function usePostgres(): boolean { return Boolean(config.databaseUrl); }