import { loadDotenv } from './env.mjs'; const DEFAULTS = { nearIntentsWsUrl: 'wss://solver-relay-v2.chaindefuser.com/ws', nearIntentsRpcUrl: 'https://solver-relay-v2.chaindefuser.com/rpc', nearBridgeRpcUrl: 'https://bridge.chaindefuser.com/rpc', nearRpcUrl: 'https://near.lava.build', nearVerifierContract: 'intents.near', nearIntentsControlApiEnabled: true, nearIntentsControlHost: '0.0.0.0', nearIntentsControlPort: 8081, marketReferenceControlPort: 8082, inventorySyncControlPort: 8083, liquidityManagerControlPort: 8084, historyWriterControlPort: 8085, strategyEngineControlPort: 8086, tradeExecutorControlPort: 8087, opsSentinelControlPort: 8088, operatorDashboardControlPort: 8090, kafkaBrokers: ['127.0.0.1:9092'], kafkaClientId: 'unrip', kafkaTopicRawNearIntentsQuote: 'raw.near_intents.quote', kafkaTopicNormSwapDemand: 'norm.swap_demand', kafkaTopicRefMarketPrice: 'ref.market_price', kafkaTopicStateIntentInventory: 'state.intent_inventory', kafkaTopicOpsLiquidityAction: 'ops.liquidity_action', kafkaTopicOpsFundingObservation: 'ops.funding_observation', kafkaTopicOpsAlert: 'ops.alert', kafkaTopicOpsEnvironmentStatus: 'ops.environment_status', kafkaTopicDecisionTradeDecision: 'decision.trade_decision', kafkaTopicCmdExecuteTrade: 'cmd.execute_trade', kafkaTopicExecTradeResult: 'exec.trade_result', kafkaConsumerGroupHistory: 'history-writer-v1', kafkaConsumerGroupInventory: 'inventory-sync-v1', kafkaConsumerGroupStrategy: 'strategy-engine-v1', kafkaConsumerGroupExecutor: 'trade-executor-v1', kafkaConsumerGroupOpsSentinel: 'ops-sentinel-v1', kafkaConsumerGroupOperatorDashboard: 'operator-dashboard-v1', historyWriterDerivedRefreshMaxEventAgeMs: 30_000, strategyStateDir: './var/strategy-state', executorStateDir: './var/executor-state', liquidityStateDir: './var/liquidity-state', postgresUrl: 'postgresql://unrip:unrip@127.0.0.1:5432/unrip', projectName: 'unrip', projectNamespace: 'unrip', tradingBtcAssetId: 'nep141:nbtc.bridge.near', tradingBtcTrackedAssetIds: [ 'nep141:nbtc.bridge.near', 'nep141:btc.omft.near', ], tradingBtcSymbol: 'BTC', tradingBtcLabel: 'BTC / nBTC reserve', tradingBtcLegacyLabel: 'BTC / legacy OMFT', tradingBtcDecimals: 8, tradingBtcChain: 'btc:mainnet', tradingBtcWithdrawAddress: '', tradingEureAssetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near', tradingEureSymbol: 'EURe', tradingEureDecimals: 18, tradingEureChain: 'eth:100', tradingEureWithdrawAddress: '', marketReferenceRefreshMs: 5_000, marketReferenceCoinGeckoRefreshMs: 15_000, marketReferenceMaxAgeMs: 30_000, marketReferenceKrakenTickerUrl: 'https://api.kraken.com/0/public/Ticker?pair=XBTEUR', marketReferenceCoinGeckoUrl: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur', inventorySyncRefreshMs: 15_000, liquidityRefreshMs: 30_000, strategyGrossThresholdPct: 0.49, strategyInitialArmed: false, strategyMaxNotionalEure: 5, strategyPriceMaxAgeMs: 30_000, strategyInventoryMaxAgeMs: 30_000, executorInitialArmed: false, executorResponseTimeoutMs: 10_000, intentRequestDefaultAmountEure: 5, intentRequestMaxAmountEure: 5, intentRequestDefaultSlippageBps: 200, intentRequestMaxSlippageBps: 200, intentRequestMinDeadlineMs: 60_000, intentRequestQuoteTimeoutMs: 10_000, intentRequestPublishTimeoutMs: 10_000, intentRequestStatusTimeoutMs: 10_000, intentRequestInventoryMaxAgeMs: 30_000, intentRequestPriceMaxAgeMs: 30_000, withdrawalsFrozen: true, btcFundingObserverEnabled: true, btcFundingObserverBaseUrl: 'https://mempool.space/api', fundingObservationStuckMs: 60 * 60 * 1000, opsSentinelEvaluationMs: 5_000, opsSentinelFundingCreditPendingMs: 5 * 60 * 1000, opsSentinelIngestMessageStaleMs: 30_000, opsSentinelIngestQuoteStaleMs: 30_000, opsSentinelIngestPublishStaleMs: 30_000, opsSentinelExecutorRelayStaleMs: 30_000, opsSentinelHistoryWriterStaleMs: 45_000, opsSentinelDashboardSourceDegradedMs: 30_000, opsSentinelSelfStaleMs: 20_000, opsSentinelAnomalyWindowSize: 6, opsSentinelAnomalyQuoteRateCollapseRatio: 0.25, opsSentinelAnomalyReconnectSpikeMultiplier: 2, opsSentinelContainmentCooldownMs: 60_000, opsSentinelAlertWebhookUrl: '', opsSentinelAlertWebhookTimeoutMs: 5_000, operatorDashboardAuthMode: 'stub', operatorDashboardAuthUsername: 'admin', operatorDashboardAuthPassword: '', operatorDashboardAuthRealm: 'unrip operator dashboard', operatorDashboardQuoteLimit: 10, operatorDashboardTradePageSize: 20, operatorDashboardUpstreamTimeoutMs: 3_000, nearIntentsStatusServicesUrl: 'https://status.near-intents.org/api/services', nearIntentsStatusPostsUrl: 'https://status.near-intents.org/api/posts?is_featured=true&limit=500', nearIntentsStatusPostEnumsUrl: 'https://status.near-intents.org/api/post_enums', nearIntentsStatusTimeoutMs: 3_000, nearIntentsStatusPollMs: 60_000, notificationNtfyBaseUrl: '', notificationNtfyTopic: 'unrip', notificationNtfyToken: '', notificationNtfyTimeoutMs: 5_000, }; function splitCsv(value) { return String(value || '') .split(',') .map((part) => part.trim()) .filter(Boolean); } function parseNumber(value, fallback) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : fallback; } function parseBoolean(value, fallback) { if (value == null || value === '') return fallback; const normalized = String(value).trim().toLowerCase(); if (['1', 'true', 'yes', 'on'].includes(normalized)) return true; if (['0', 'false', 'no', 'off'].includes(normalized)) return false; return fallback; } function unique(values) { return [...new Set(values.filter(Boolean))]; } function buildAsset({ assetId, symbol, decimals, chain, withdrawAddress = '', label = symbol, role = 'tracked', }) { return { assetId, symbol, label, decimals, chain, withdrawAddress, role, }; } function buildBtcAssetLabel(assetId, tradingAssetId) { if (assetId === tradingAssetId) return DEFAULTS.tradingBtcLabel; if (assetId === 'nep141:btc.omft.near') return DEFAULTS.tradingBtcLegacyLabel; return `${DEFAULTS.tradingBtcSymbol} / ${assetId.replace(/^nep141:/, '')}`; } function defaultControlBaseUrl({ serviceName, port, namespace }) { if (process.env.KUBERNETES_SERVICE_HOST) { return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`; } return `http://127.0.0.1:${port}`; } export function loadConfig({ envPath = '.env' } = {}) { loadDotenv(envPath); const tradingBtc = buildAsset({ assetId: DEFAULTS.tradingBtcAssetId, symbol: DEFAULTS.tradingBtcSymbol, label: DEFAULTS.tradingBtcLabel, decimals: DEFAULTS.tradingBtcDecimals, chain: DEFAULTS.tradingBtcChain, withdrawAddress: DEFAULTS.tradingBtcWithdrawAddress, role: 'trading', }); const trackedBtcAssetIds = unique([ tradingBtc.assetId, ...DEFAULTS.tradingBtcTrackedAssetIds, ]); const tradingBtcAssets = trackedBtcAssetIds.map((assetId) => { if (assetId === tradingBtc.assetId) return tradingBtc; return buildAsset({ assetId, symbol: tradingBtc.symbol, label: buildBtcAssetLabel(assetId, tradingBtc.assetId), decimals: tradingBtc.decimals, chain: tradingBtc.chain, withdrawAddress: tradingBtc.withdrawAddress, role: 'legacy', }); }); const tradingEure = buildAsset({ assetId: DEFAULTS.tradingEureAssetId, symbol: DEFAULTS.tradingEureSymbol, label: DEFAULTS.tradingEureSymbol, decimals: DEFAULTS.tradingEureDecimals, chain: DEFAULTS.tradingEureChain, withdrawAddress: DEFAULTS.tradingEureWithdrawAddress, role: 'trading', }); const trackedAssets = [ ...tradingBtcAssets, tradingEure, ]; const projectName = process.env.PROJECT_NAME || DEFAULTS.projectName; const projectNamespace = process.env.PROJECT_NAMESPACE || projectName || DEFAULTS.projectNamespace; return { nearIntentsApiKey: process.env.NEAR_INTENTS_API_KEY || '', nearIntentsAccountId: process.env.NEAR_INTENTS_ACCOUNT_ID || '', nearIntentsSignerPrivateKey: process.env.NEAR_INTENTS_SIGNER_PRIVATE_KEY || '', nearIntentsWsUrl: process.env.NEAR_INTENTS_WS_URL || DEFAULTS.nearIntentsWsUrl, nearIntentsRpcUrl: process.env.NEAR_INTENTS_RPC_URL || DEFAULTS.nearIntentsRpcUrl, nearBridgeRpcUrl: process.env.NEAR_INTENTS_BRIDGE_RPC_URL || DEFAULTS.nearBridgeRpcUrl, nearRpcUrl: process.env.NEAR_RPC_URL || DEFAULTS.nearRpcUrl, nearVerifierContract: process.env.NEAR_INTENTS_VERIFIER_CONTRACT || DEFAULTS.nearVerifierContract, nearIntentsControlApiEnabled: parseBoolean( process.env.NEAR_INTENTS_CONTROL_API_ENABLED, DEFAULTS.nearIntentsControlApiEnabled, ), nearIntentsControlHost: process.env.NEAR_INTENTS_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, nearIntentsControlPort: parseNumber( process.env.NEAR_INTENTS_CONTROL_PORT, DEFAULTS.nearIntentsControlPort, ), nearIntentsControlBaseUrl: process.env.NEAR_INTENTS_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'near-intents-ingest', port: parseNumber( process.env.NEAR_INTENTS_CONTROL_PORT, DEFAULTS.nearIntentsControlPort, ), namespace: projectNamespace, }), marketReferenceControlHost: process.env.MARKET_REFERENCE_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, marketReferenceControlPort: parseNumber( process.env.MARKET_REFERENCE_CONTROL_PORT, DEFAULTS.marketReferenceControlPort, ), marketReferenceControlBaseUrl: process.env.MARKET_REFERENCE_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'market-reference-ingest', port: parseNumber( process.env.MARKET_REFERENCE_CONTROL_PORT, DEFAULTS.marketReferenceControlPort, ), namespace: projectNamespace, }), inventorySyncControlHost: process.env.INVENTORY_SYNC_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, inventorySyncControlPort: parseNumber( process.env.INVENTORY_SYNC_CONTROL_PORT, DEFAULTS.inventorySyncControlPort, ), inventorySyncControlBaseUrl: process.env.INVENTORY_SYNC_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'inventory-sync', port: parseNumber( process.env.INVENTORY_SYNC_CONTROL_PORT, DEFAULTS.inventorySyncControlPort, ), namespace: projectNamespace, }), liquidityManagerControlHost: process.env.LIQUIDITY_MANAGER_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, liquidityManagerControlPort: parseNumber( process.env.LIQUIDITY_MANAGER_CONTROL_PORT, DEFAULTS.liquidityManagerControlPort, ), liquidityManagerControlBaseUrl: process.env.LIQUIDITY_MANAGER_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'liquidity-manager', port: parseNumber( process.env.LIQUIDITY_MANAGER_CONTROL_PORT, DEFAULTS.liquidityManagerControlPort, ), namespace: projectNamespace, }), historyWriterControlHost: process.env.HISTORY_WRITER_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, historyWriterControlPort: parseNumber( process.env.HISTORY_WRITER_CONTROL_PORT, DEFAULTS.historyWriterControlPort, ), historyWriterControlBaseUrl: process.env.HISTORY_WRITER_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'history-writer', port: parseNumber( process.env.HISTORY_WRITER_CONTROL_PORT, DEFAULTS.historyWriterControlPort, ), namespace: projectNamespace, }), opsSentinelControlHost: process.env.OPS_SENTINEL_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, opsSentinelControlPort: parseNumber( process.env.OPS_SENTINEL_CONTROL_PORT, DEFAULTS.opsSentinelControlPort, ), opsSentinelControlBaseUrl: process.env.OPS_SENTINEL_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'ops-sentinel', port: parseNumber( process.env.OPS_SENTINEL_CONTROL_PORT, DEFAULTS.opsSentinelControlPort, ), namespace: projectNamespace, }), strategyEngineControlHost: process.env.STRATEGY_ENGINE_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, strategyEngineControlPort: parseNumber( process.env.STRATEGY_ENGINE_CONTROL_PORT, DEFAULTS.strategyEngineControlPort, ), strategyEngineControlBaseUrl: process.env.STRATEGY_ENGINE_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'strategy-engine', port: parseNumber( process.env.STRATEGY_ENGINE_CONTROL_PORT, DEFAULTS.strategyEngineControlPort, ), namespace: projectNamespace, }), tradeExecutorControlHost: process.env.TRADE_EXECUTOR_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, tradeExecutorControlPort: parseNumber( process.env.TRADE_EXECUTOR_CONTROL_PORT, DEFAULTS.tradeExecutorControlPort, ), tradeExecutorControlBaseUrl: process.env.TRADE_EXECUTOR_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'trade-executor', port: parseNumber( process.env.TRADE_EXECUTOR_CONTROL_PORT, DEFAULTS.tradeExecutorControlPort, ), namespace: projectNamespace, }), operatorDashboardControlHost: process.env.OPERATOR_DASHBOARD_CONTROL_HOST || DEFAULTS.nearIntentsControlHost, operatorDashboardControlPort: parseNumber( process.env.OPERATOR_DASHBOARD_CONTROL_PORT, DEFAULTS.operatorDashboardControlPort, ), operatorDashboardControlBaseUrl: process.env.OPERATOR_DASHBOARD_CONTROL_BASE_URL || defaultControlBaseUrl({ serviceName: 'operator-dashboard', port: parseNumber( process.env.OPERATOR_DASHBOARD_CONTROL_PORT, DEFAULTS.operatorDashboardControlPort, ), namespace: projectNamespace, }), kafkaBrokers: splitCsv(process.env.KAFKA_BROKERS).length ? splitCsv(process.env.KAFKA_BROKERS) : DEFAULTS.kafkaBrokers, kafkaClientId: process.env.KAFKA_CLIENT_ID || DEFAULTS.kafkaClientId, kafkaTopicRawNearIntentsQuote: process.env.KAFKA_TOPIC_RAW_NEAR_INTENTS_QUOTE || DEFAULTS.kafkaTopicRawNearIntentsQuote, kafkaTopicNormSwapDemand: process.env.KAFKA_TOPIC_NORM_SWAP_DEMAND || DEFAULTS.kafkaTopicNormSwapDemand, kafkaTopicRefMarketPrice: process.env.KAFKA_TOPIC_REF_MARKET_PRICE || DEFAULTS.kafkaTopicRefMarketPrice, kafkaTopicStateIntentInventory: process.env.KAFKA_TOPIC_STATE_INTENT_INVENTORY || DEFAULTS.kafkaTopicStateIntentInventory, kafkaTopicOpsLiquidityAction: process.env.KAFKA_TOPIC_OPS_LIQUIDITY_ACTION || DEFAULTS.kafkaTopicOpsLiquidityAction, kafkaTopicOpsFundingObservation: process.env.KAFKA_TOPIC_OPS_FUNDING_OBSERVATION || DEFAULTS.kafkaTopicOpsFundingObservation, kafkaTopicOpsAlert: process.env.KAFKA_TOPIC_OPS_ALERT || DEFAULTS.kafkaTopicOpsAlert, kafkaTopicOpsEnvironmentStatus: process.env.KAFKA_TOPIC_OPS_ENVIRONMENT_STATUS || DEFAULTS.kafkaTopicOpsEnvironmentStatus, kafkaTopicDecisionTradeDecision: process.env.KAFKA_TOPIC_DECISION_TRADE_DECISION || DEFAULTS.kafkaTopicDecisionTradeDecision, kafkaTopicCmdExecuteTrade: process.env.KAFKA_TOPIC_CMD_EXECUTE_TRADE || DEFAULTS.kafkaTopicCmdExecuteTrade, kafkaTopicExecTradeResult: process.env.KAFKA_TOPIC_EXEC_TRADE_RESULT || DEFAULTS.kafkaTopicExecTradeResult, kafkaConsumerGroupHistory: process.env.KAFKA_CONSUMER_GROUP_HISTORY || DEFAULTS.kafkaConsumerGroupHistory, kafkaConsumerGroupInventory: process.env.KAFKA_CONSUMER_GROUP_INVENTORY || DEFAULTS.kafkaConsumerGroupInventory, kafkaConsumerGroupStrategy: process.env.KAFKA_CONSUMER_GROUP_STRATEGY || DEFAULTS.kafkaConsumerGroupStrategy, kafkaConsumerGroupExecutor: process.env.KAFKA_CONSUMER_GROUP_EXECUTOR || DEFAULTS.kafkaConsumerGroupExecutor, kafkaConsumerGroupOpsSentinel: process.env.KAFKA_CONSUMER_GROUP_OPS_SENTINEL || DEFAULTS.kafkaConsumerGroupOpsSentinel, kafkaConsumerGroupOperatorDashboard: process.env.KAFKA_CONSUMER_GROUP_OPERATOR_DASHBOARD || DEFAULTS.kafkaConsumerGroupOperatorDashboard, historyWriterDerivedRefreshMaxEventAgeMs: parseNumber( process.env.HISTORY_WRITER_DERIVED_REFRESH_MAX_EVENT_AGE_MS, DEFAULTS.historyWriterDerivedRefreshMaxEventAgeMs, ), strategyStateDir: process.env.STRATEGY_STATE_DIR || DEFAULTS.strategyStateDir, executorStateDir: process.env.EXECUTOR_STATE_DIR || DEFAULTS.executorStateDir, liquidityStateDir: process.env.LIQUIDITY_STATE_DIR || DEFAULTS.liquidityStateDir, postgresUrl: process.env.POSTGRES_URL || DEFAULTS.postgresUrl, projectName, projectNamespace, tradingBtc, tradingBtcAssets, tradingEure, activePair: `${tradingBtc.assetId}->${tradingEure.assetId}`, activeAssetIds: [tradingBtc.assetId, tradingEure.assetId], trackedAssets, trackedAssetIds: trackedAssets.map((asset) => asset.assetId), assetRegistry: new Map(trackedAssets.map((asset) => [asset.assetId, asset])), marketReferenceRefreshMs: parseNumber( process.env.MARKET_REFERENCE_REFRESH_MS, DEFAULTS.marketReferenceRefreshMs, ), marketReferenceCoinGeckoRefreshMs: parseNumber( process.env.MARKET_REFERENCE_COINGECKO_REFRESH_MS, DEFAULTS.marketReferenceCoinGeckoRefreshMs, ), marketReferenceMaxAgeMs: parseNumber( process.env.MARKET_REFERENCE_MAX_AGE_MS, DEFAULTS.marketReferenceMaxAgeMs, ), marketReferenceKrakenTickerUrl: process.env.MARKET_REFERENCE_KRAKEN_TICKER_URL || DEFAULTS.marketReferenceKrakenTickerUrl, marketReferenceCoinGeckoUrl: process.env.MARKET_REFERENCE_COINGECKO_URL || DEFAULTS.marketReferenceCoinGeckoUrl, inventorySyncRefreshMs: parseNumber( process.env.INVENTORY_SYNC_REFRESH_MS, DEFAULTS.inventorySyncRefreshMs, ), liquidityRefreshMs: parseNumber( process.env.LIQUIDITY_REFRESH_MS, DEFAULTS.liquidityRefreshMs, ), strategyGrossThresholdPct: DEFAULTS.strategyGrossThresholdPct, strategyInitialArmed: parseBoolean( process.env.STRATEGY_INITIAL_ARMED, DEFAULTS.strategyInitialArmed, ), strategyMaxNotionalEure: DEFAULTS.strategyMaxNotionalEure, strategyPriceMaxAgeMs: DEFAULTS.strategyPriceMaxAgeMs, strategyInventoryMaxAgeMs: DEFAULTS.strategyInventoryMaxAgeMs, executorInitialArmed: parseBoolean( process.env.EXECUTOR_INITIAL_ARMED, DEFAULTS.executorInitialArmed, ), executorResponseTimeoutMs: parseNumber( process.env.EXECUTOR_RESPONSE_TIMEOUT_MS, DEFAULTS.executorResponseTimeoutMs, ), intentRequestDefaultAmountEure: DEFAULTS.intentRequestDefaultAmountEure, intentRequestMaxAmountEure: DEFAULTS.intentRequestMaxAmountEure, intentRequestDefaultSlippageBps: DEFAULTS.intentRequestDefaultSlippageBps, intentRequestMaxSlippageBps: DEFAULTS.intentRequestMaxSlippageBps, intentRequestMinDeadlineMs: DEFAULTS.intentRequestMinDeadlineMs, intentRequestQuoteTimeoutMs: parseNumber( process.env.INTENT_REQUEST_QUOTE_TIMEOUT_MS, DEFAULTS.intentRequestQuoteTimeoutMs, ), intentRequestPublishTimeoutMs: parseNumber( process.env.INTENT_REQUEST_PUBLISH_TIMEOUT_MS, DEFAULTS.intentRequestPublishTimeoutMs, ), intentRequestStatusTimeoutMs: parseNumber( process.env.INTENT_REQUEST_STATUS_TIMEOUT_MS, DEFAULTS.intentRequestStatusTimeoutMs, ), intentRequestInventoryMaxAgeMs: DEFAULTS.intentRequestInventoryMaxAgeMs, intentRequestPriceMaxAgeMs: DEFAULTS.intentRequestPriceMaxAgeMs, withdrawalsFrozen: parseBoolean( process.env.LIQUIDITY_WITHDRAWALS_FROZEN, DEFAULTS.withdrawalsFrozen, ), btcFundingObserverEnabled: parseBoolean( process.env.BTC_FUNDING_OBSERVER_ENABLED, DEFAULTS.btcFundingObserverEnabled, ), btcFundingObserverBaseUrl: process.env.BTC_FUNDING_OBSERVER_BASE_URL || DEFAULTS.btcFundingObserverBaseUrl, fundingObservationStuckMs: parseNumber( process.env.FUNDING_OBSERVATION_STUCK_MS, DEFAULTS.fundingObservationStuckMs, ), opsSentinelEvaluationMs: parseNumber( process.env.OPS_SENTINEL_EVALUATION_MS, DEFAULTS.opsSentinelEvaluationMs, ), opsSentinelPriceStaleMs: parseNumber( process.env.OPS_SENTINEL_PRICE_STALE_MS, DEFAULTS.marketReferenceMaxAgeMs, ), opsSentinelInventoryStaleMs: parseNumber( process.env.OPS_SENTINEL_INVENTORY_STALE_MS, DEFAULTS.strategyInventoryMaxAgeMs, ), opsSentinelFundingCreditPendingMs: parseNumber( process.env.OPS_SENTINEL_FUNDING_CREDIT_PENDING_MS, DEFAULTS.opsSentinelFundingCreditPendingMs, ), opsSentinelFundingStuckMs: parseNumber( process.env.OPS_SENTINEL_FUNDING_STUCK_MS, DEFAULTS.fundingObservationStuckMs, ), opsSentinelIngestMessageStaleMs: parseNumber( process.env.OPS_SENTINEL_INGEST_MESSAGE_STALE_MS, DEFAULTS.opsSentinelIngestMessageStaleMs, ), opsSentinelIngestQuoteStaleMs: parseNumber( process.env.OPS_SENTINEL_INGEST_QUOTE_STALE_MS, DEFAULTS.opsSentinelIngestQuoteStaleMs, ), opsSentinelIngestPublishStaleMs: parseNumber( process.env.OPS_SENTINEL_INGEST_PUBLISH_STALE_MS, DEFAULTS.opsSentinelIngestPublishStaleMs, ), opsSentinelExecutorRelayStaleMs: parseNumber( process.env.OPS_SENTINEL_EXECUTOR_RELAY_STALE_MS, DEFAULTS.opsSentinelExecutorRelayStaleMs, ), opsSentinelHistoryWriterStaleMs: parseNumber( process.env.OPS_SENTINEL_HISTORY_WRITER_STALE_MS, DEFAULTS.opsSentinelHistoryWriterStaleMs, ), opsSentinelDashboardSourceDegradedMs: parseNumber( process.env.OPS_SENTINEL_DASHBOARD_SOURCE_DEGRADED_MS, DEFAULTS.opsSentinelDashboardSourceDegradedMs, ), opsSentinelSelfStaleMs: parseNumber( process.env.OPS_SENTINEL_SELF_STALE_MS, DEFAULTS.opsSentinelSelfStaleMs, ), opsSentinelAnomalyWindowSize: parseNumber( process.env.OPS_SENTINEL_ANOMALY_WINDOW_SIZE, DEFAULTS.opsSentinelAnomalyWindowSize, ), opsSentinelAnomalyQuoteRateCollapseRatio: parseNumber( process.env.OPS_SENTINEL_ANOMALY_QUOTE_RATE_COLLAPSE_RATIO, DEFAULTS.opsSentinelAnomalyQuoteRateCollapseRatio, ), opsSentinelAnomalyReconnectSpikeMultiplier: parseNumber( process.env.OPS_SENTINEL_ANOMALY_RECONNECT_SPIKE_MULTIPLIER, DEFAULTS.opsSentinelAnomalyReconnectSpikeMultiplier, ), opsSentinelContainmentCooldownMs: parseNumber( process.env.OPS_SENTINEL_CONTAINMENT_COOLDOWN_MS, DEFAULTS.opsSentinelContainmentCooldownMs, ), opsSentinelAlertWebhookUrl: process.env.OPS_SENTINEL_ALERT_WEBHOOK_URL || DEFAULTS.opsSentinelAlertWebhookUrl, opsSentinelAlertWebhookTimeoutMs: parseNumber( process.env.OPS_SENTINEL_ALERT_WEBHOOK_TIMEOUT_MS, DEFAULTS.opsSentinelAlertWebhookTimeoutMs, ), operatorDashboardAuthMode: process.env.OPERATOR_DASHBOARD_AUTH_MODE || DEFAULTS.operatorDashboardAuthMode, operatorDashboardAuthUsername: process.env.OPERATOR_DASHBOARD_AUTH_USERNAME || DEFAULTS.operatorDashboardAuthUsername, operatorDashboardAuthPassword: process.env.OPERATOR_DASHBOARD_AUTH_PASSWORD || DEFAULTS.operatorDashboardAuthPassword, operatorDashboardAuthRealm: process.env.OPERATOR_DASHBOARD_AUTH_REALM || DEFAULTS.operatorDashboardAuthRealm, operatorDashboardQuoteLimit: parseNumber( process.env.OPERATOR_DASHBOARD_QUOTE_LIMIT, DEFAULTS.operatorDashboardQuoteLimit, ), operatorDashboardTradePageSize: parseNumber( process.env.OPERATOR_DASHBOARD_TRADE_PAGE_SIZE, DEFAULTS.operatorDashboardTradePageSize, ), operatorDashboardUpstreamTimeoutMs: parseNumber( process.env.OPERATOR_DASHBOARD_UPSTREAM_TIMEOUT_MS, DEFAULTS.operatorDashboardUpstreamTimeoutMs, ), nearIntentsStatusServicesUrl: process.env.NEAR_INTENTS_STATUS_SERVICES_URL || DEFAULTS.nearIntentsStatusServicesUrl, nearIntentsStatusPostsUrl: process.env.NEAR_INTENTS_STATUS_POSTS_URL || DEFAULTS.nearIntentsStatusPostsUrl, nearIntentsStatusPostEnumsUrl: process.env.NEAR_INTENTS_STATUS_POST_ENUMS_URL || DEFAULTS.nearIntentsStatusPostEnumsUrl, nearIntentsStatusTimeoutMs: parseNumber( process.env.NEAR_INTENTS_STATUS_TIMEOUT_MS, DEFAULTS.nearIntentsStatusTimeoutMs, ), nearIntentsStatusPollMs: parseNumber( process.env.NEAR_INTENTS_STATUS_POLL_MS, DEFAULTS.nearIntentsStatusPollMs, ), notificationNtfyBaseUrl: process.env.NOTIFICATION_NTFY_BASE_URL || DEFAULTS.notificationNtfyBaseUrl, notificationNtfyTopic: process.env.NOTIFICATION_NTFY_TOPIC || DEFAULTS.notificationNtfyTopic, notificationNtfyToken: process.env.NOTIFICATION_NTFY_TOKEN || DEFAULTS.notificationNtfyToken, notificationNtfyTimeoutMs: parseNumber( process.env.NOTIFICATION_NTFY_TIMEOUT_MS, DEFAULTS.notificationNtfyTimeoutMs, ), }; }