From 169fbe53b81bec894793d554bb69dd9510a481b3 Mon Sep 17 00:00:00 2001 From: talktosam2003 Date: Sat, 25 Jul 2026 18:33:42 +0100 Subject: [PATCH] Environment Variables --- ENVIRONMENT_VARIABLES_AND_SECRETS.md | 602 +++++++++++++++++---------- dashboard/.env.example | 47 ++- frontend/.env.example | 14 + listener/.env.example | 270 ++++++++++-- 4 files changed, 679 insertions(+), 254 deletions(-) create mode 100644 frontend/.env.example diff --git a/ENVIRONMENT_VARIABLES_AND_SECRETS.md b/ENVIRONMENT_VARIABLES_AND_SECRETS.md index 083c763..1da1650 100644 --- a/ENVIRONMENT_VARIABLES_AND_SECRETS.md +++ b/ENVIRONMENT_VARIABLES_AND_SECRETS.md @@ -1,149 +1,187 @@ # Environment Variables and Secrets (NotifyChain) -> Centralized documentation for environment variables and secret management requirements across NotifyChain services. +> Centralized documentation for all environment variables and secret management +> requirements across NotifyChain services. + +**Quick links** +- [1. Services covered](#1-services-covered) +- [2. Listener service variables](#2-listener-service-variables) +- [3. Dashboard service variables](#3-dashboard-service-variables) +- [4. Frontend service variables](#4-frontend-service-variables) +- [5. CI/CD secrets (GitHub Actions)](#5-cicd-secrets-github-actions) +- [6. Task Bounty contract deployment (Ethereum)](#6-task-bounty-contract-deployment-ethereum) +- [7. Sample .env files](#7-sample-env-files) +- [8. Security recommendations](#8-security-recommendations) +- [9. Operational notes and troubleshooting](#9-operational-notes-and-troubleshooting) -This document is based on the listener service configuration found in `listener/src/config.ts`. +--- ## 1. Services covered -- **Listener service** (`listener/`): the Node.js/TypeScript long-running process that: - - polls Stellar for contract events - - deduplicates and stores events - - dispatches notifications (Discord/webhooks) - - exposes HTTP APIs - - schedules retries and scheduled notifications +| Service | Path | Runtime | Config source | +|---|---|---|---| +| **Listener** | `listener/` | Node.js / TypeScript | `process.env` — parsed in `listener/src/config.ts` | +| **Dashboard** | `dashboard/` | Vite / React (browser) | `import.meta.env` — `VITE_*` prefix required | +| **Frontend** | `frontend/` | Create React App / React (browser) | `process.env` — `REACT_APP_*` prefix required | +| **Task Bounty** | `Documents/Task Bounty/` | Hardhat deployment scripts | `process.env` / `.env` loaded by `dotenv` | + +The Rust smart contracts in `contract/` contain no runtime environment variables — all configuration is on-chain. + +--- -- **Dashboard service** (`dashboard/` and/or `frontend/`): this repo currently does not define a runtime server-side secret surface in code we accessed here. (Dashboard configuration is handled via its build tooling; sensitive values should still be avoided or proxied through the listener.) +## 2. Listener service variables -## 2. Environment variables list (Listener) +The listener is the only service with server-side secrets. All variables below are read from `process.env` in `listener/src/config.ts` (and supporting files) at startup. -### 2.1 Stellar / chain connectivity +### 2.1 Logging / runtime -| Variable | Purpose | Format | Default (from code) | Notes | -|---|---|---|---|---| -| `STELLAR_NETWORK` | Stellar network label used by components that need a network name | string | `testnet` | Set to `testnet`/`public`/etc as required. | -| `STELLAR_RPC_URL` | Stellar Horizon / RPC base URL used to fetch events/ledgers | string | `https://soroban-testnet.stellar.org:443` | Override in production to your own endpoint. | -| `STELLAR_NETWORK_PASSPHRASE` | Network passphrase used for Stellar SDK / signature context | string | `Test SDF Network ; September 2015` | Must match the network you are targeting. | +| Variable | Default | Required | Description | +|---|---|---|---| +| `LOG_LEVEL` | `info` | No | Winston log level. Values: `error` \| `warn` \| `info` \| `http` \| `verbose` \| `debug` \| `silly`. | +| `NODE_ENV` | *(unset)* | No | Set to `production` to enable newline-delimited JSON (structured) log output. Leave unset for human-readable pretty-print during development. | -### 2.2 Contract event selection +### 2.2 Stellar / chain connectivity -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `CONTRACT_ADDRESSES` | Which contract addresses to monitor and which event names to allow | JSON array | `[]` | **Required shape**: `[ { "address": "", "events": ["", ...] }, ... ]` | +| Variable | Default | Required | Description | +|---|---|---|---| +| `STELLAR_NETWORK` | `testnet` | No | Stellar network label. Common values: `testnet`, `public`. | +| `STELLAR_RPC_URL` | `https://soroban-testnet.stellar.org:443` | No | Soroban RPC endpoint. Override in production to a dedicated node. | +| `STELLAR_NETWORK_PASSPHRASE` | `Test SDF Network ; September 2015` | No | Network passphrase used for Stellar SDK and signature context. Must match the target network. Mainnet value: `Public Global Stellar Network ; September 2015`. | -#### Example +### 2.3 Contract event selection + +| Variable | Default | Required | Description | +|---|---|---|---| +| `CONTRACT_ADDRESSES` | `[]` | **Yes** (for any useful work) | JSON array of contract objects to monitor. See shape below. | + +**Shape:** ```json [ { - "address": "123abc...", - "events": ["TaskCreated", "WorkSubmitted"] - }, - { - "address": "456def...", - "events": ["AutoshareCreated", "GroupActivated"] + "address": "", + "events": ["EventName1", "EventName2"] } ] ``` +Use `"*"` as the sole event entry to subscribe to all events from a contract. -### 2.3 Listener core polling / reconnect +### 2.4 Listener HTTP API server -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `POLL_INTERVAL_MS` | Subscriber polling interval | integer (ms) | `30000` | Controls how often the listener checks for new events. | -| `MAX_RECONNECT_ATTEMPTS` | Reconnect attempts for RPC failures | integer | `5` | | -| `RECONNECT_DELAY_MS` | Delay between reconnect attempts | integer (ms) | `5000` | | +| Variable | Default | Required | Description | +|---|---|---|---| +| `EVENTS_API_PORT` | `8787` | No | Port the listener HTTP server binds to. | +| `EVENTS_API_CORS_ORIGIN` | `http://localhost:5173` | No | Allowed CORS origin. Set to your dashboard URL in production. Avoid `*`. | -### 2.4 Events HTTP server +### 2.5 Database -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `EVENTS_API_PORT` | Port for listener HTTP server | integer | `8787` | | -| `EVENTS_API_CORS_ORIGIN` | Allowed CORS origin for the events API | string | `http://localhost:5173` | Set to your dashboard origin(s) in production. | +| Variable | Default | Required | Description | +|---|---|---|---| +| `DATABASE_PATH` | `./data/notifications.db` | No | SQLite database file path. Used for event deduplication, cursor tracking, and scheduler state. Ensure the path is persistent and writable in your deployment. | -### 2.5 Database +### 2.6 Discord delivery + +Both variables must be provided together or neither. -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `DATABASE_PATH` | SQLite database file path used for deduplication/cursors and scheduler state | string | `./data/notifications.db` | Ensure the path is persistent and writable. | +| Variable | Default | Required | Description | +|---|---|---|---| +| `DISCORD_WEBHOOK_URL` | *(none)* | Conditional | Full Discord webhook URL from Server Settings → Integrations → Webhooks. ⚠️ Secret. | +| `DISCORD_WEBHOOK_ID` | *(none)* | Conditional | Numeric Discord webhook ID (portion of the URL). ⚠️ Secret. | +| `DISCORD_RETRY_COUNT` | *(uses `RETRY_MAX_RETRIES`)* | No | Override the number of delivery retries specifically for Discord notifications. | +| `DISCORD_BACKOFF_BASE_SECONDS` | *(uses `RETRY_BASE_DELAY_MS` / 1000)* | No | Override the exponential backoff base (in seconds) for Discord delivery retries. | +| `NOTIFICATION_DEDUPLICATION_WINDOW_MS` | `60000` | No | Window (ms) within which duplicate Discord sends are suppressed. | +| `NOTIFICATION_DEDUPLICATION_MAX_SIZE` | `10000` | No | Maximum entries held in the Discord deduplication cache. | -### 2.6 Discord delivery (webhook-based) +### 2.7 Webhook security -The listener supports Discord notifications via webhook URL and ID. +| Variable | Default | Required | Description | +|---|---|---|---| +| `WEBHOOK_SECRETS` | `[]` | No | JSON array of `{id, secret}` objects used to sign and verify outgoing webhook payloads. ⚠️ Secret. | +| `PAYLOAD_INTEGRITY_SECRET` | *(none)* | No | HMAC-SHA256 secret for hashing scheduled notification payloads to detect tampering. ⚠️ Secret. Generate with `openssl rand -base64 32`. | -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `DISCORD_WEBHOOK_URL` | Discord webhook URL | string | *(optional)* | If provided, `DISCORD_WEBHOOK_ID` must also be provided. | -| `DISCORD_WEBHOOK_ID` | Discord webhook ID | string | *(optional)* | If provided, `DISCORD_WEBHOOK_URL` must also be provided. | -| `NOTIFICATION_DEDUPLICATION_WINDOW_MS` | Dedup window used for Discord notifications | integer (ms) | `60000` | | -| `NOTIFICATION_DEDUPLICATION_MAX_SIZE` | Max entries for dedup cache/window | integer | `10000` | | +**`WEBHOOK_SECRETS` shape:** +```json +[ + { "id": "target-1", "secret": "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, + { "id": "target-2", "secret": "whsec_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } +] +``` -### 2.7 Webhook secret management +### 2.8 API keys -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `WEBHOOK_SECRETS` | Secrets required to verify incoming/outgoing webhook identities (as implemented in listener code) | JSON array | `[]` | **Required shape**: `[ { "id": "", "secret": "" }, ... ]` | +| Variable | Default | Required | Description | +|---|---|---|---| +| `API_KEYS` | `[]` | No | JSON array of `{key, name?}` objects. Clients must supply a matching key to access protected listener API endpoints. ⚠️ Secret. | -#### Example +**Shape:** ```json [ - { - "id": "target-1", - "secret": "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - }, - { - "id": "target-2", - "secret": "whsec_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" - } + { "key": "sk_live_abc123", "name": "dashboard-prod" } ] ``` -### 2.8 Cleanup / retention policies - -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `CLEANUP_INTERVAL_MS` | Interval for cleanup job | integer (ms) | `3600000` (1h) | | -| `NOTIFICATION_RETENTION_MS` | How long notifications/events are retained | integer (ms) | `604800000` (7d) | | -| `RATE_LIMIT_EVENT_RETENTION_MS` | How long rate-limit tracking is retained | integer (ms) | `86400000` (24h) | | -| `EVENT_RETENTION_MS` | How long raw events are retained | integer (ms) | `86400000` (24h) | | - -### 2.9 Scheduler subsystem (scheduled notifications) - -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `SCHEDULER_ENABLED` | Enable scheduled notifications processing | boolean-like string | *(enabled unless `false`)* | Code: `trimEnv('SCHEDULER_ENABLED') !== 'false'` | -| `SCHEDULER_POLL_INTERVAL_MS` | Scheduler tick interval | integer (ms) | `10000` | | -| `SCHEDULER_LOCK_TIMEOUT_MS` | Lock timeout for worker acquisition | integer (ms) | `60000` | | -| `SCHEDULER_PROCESSOR_ID` | Identifier for this scheduler worker instance | string | *(optional)* | Useful for debugging multi-instance deployments. | -| `SCHEDULER_BATCH_SIZE` | How many due notifications to fetch/process per tick | integer | `10` | | -| `SCHEDULER_TIMING_BUFFER_MS` | Timing buffer to avoid late/early dispatch edges | integer (ms) | `60000` | | - -### 2.10 Retry scheduler subsystem - -Used for retrying failed deliveries/processing. - -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `RETRY_SCHEDULER_ENABLED` | Enable retry scheduler | boolean-like string | *(enabled unless `false`)* | `!== 'false'` semantics | -| `RETRY_SCHEDULER_POLL_INTERVAL_MS` | Retry scheduler tick interval | integer (ms) | `15000` | | -| `RETRY_SCHEDULER_LOCK_TIMEOUT_MS` | Lock timeout for retry worker acquisition | integer (ms) | `60000` | | -| `RETRY_SCHEDULER_PROCESSOR_ID` | Processor identifier for retry worker | string | *(optional)* | | -| `RETRY_SCHEDULER_BATCH_SIZE` | Retry jobs processed per tick | integer | `10` | | -| `RETRY_BASE_DELAY_MS` | Base delay for exponential backoff | integer (ms) | `5000` | | -| `RETRY_MULTIPLIER` | Exponential backoff multiplier | integer | `2` | | -| `RETRY_MAX_RETRIES` | Max number of retries | integer | `5` | | -| `RETRY_JITTER` | Whether to apply jitter to delays | boolean-like string | *(enabled unless `false`)* | Code: `trimEnv('RETRY_JITTER') !== 'false'` | -| `RETRY_MAX_DELAY_MS` | Maximum delay clamp | integer (ms) | `3600000` (1h) | | - -### 2.11 Rate limiting - -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `RATE_LIMIT_ENABLED` | Enable rate limiting | boolean-like string | *(enabled unless `false`)* | `!== 'false'` semantics | -| `RATE_LIMIT_WINDOW_MS` | Rate limit window size | integer (ms) | `60000` | | -| `RATE_LIMIT_MAX_REQUESTS` | Max requests per window | integer | `60` | | -| `RATE_LIMIT_CLIENT_OVERRIDES` | Per-client overrides | JSON object | `{}` | **Shape**: `{ "": { "maxRequests": , "windowMs": }, ... }` | - -#### Example +### 2.9 Polling / reconnect + +| Variable | Default | Required | Description | +|---|---|---|---| +| `POLL_INTERVAL_MS` | `30000` | No | How often the listener polls Stellar for new contract events (ms). | +| `MAX_RECONNECT_ATTEMPTS` | `5` | No | Maximum number of reconnect attempts when the RPC endpoint fails. | +| `RECONNECT_DELAY_MS` | `5000` | No | Delay between reconnect attempts (ms). | + +### 2.10 Retry queue (in-memory) + +The in-memory retry queue handles fast retries for transient failures within the current process lifetime. + +| Variable | Default | Required | Description | +|---|---|---|---| +| `RETRY_BASE_DELAY_MS` | `5000` | No | Base delay for exponential backoff (ms). | +| `RETRY_MAX_RETRIES` | `5` | No | Maximum retry attempts before a job is marked permanently failed. | +| `RETRY_MULTIPLIER` | `2` | No | Exponential backoff multiplier. Delay = base × multiplier^attempt. | +| `RETRY_MAX_DELAY_MS` | `3600000` | No | Maximum clamped retry delay (ms). Default: 1 hour. | +| `RETRY_JITTER` | `true` | No | Add random jitter to retry delays to avoid thundering-herd. Set to `false` to disable. | +| `RETRY_QUEUE_PROCESS_INTERVAL_MS` | `5000` | No | How often the in-memory retry queue is drained (ms). | + +### 2.11 Retry scheduler (database-backed) + +The DB-backed retry scheduler persists retry state across process restarts. + +| Variable | Default | Required | Description | +|---|---|---|---| +| `RETRY_SCHEDULER_ENABLED` | `true` | No | Enable the database-backed retry scheduler. Set to `false` to disable. | +| `RETRY_SCHEDULER_POLL_INTERVAL_MS` | `15000` | No | How often the retry scheduler checks for due jobs (ms). | +| `RETRY_SCHEDULER_LOCK_TIMEOUT_MS` | `60000` | No | Worker lock timeout (ms). Prevents two instances processing the same job. | +| `RETRY_SCHEDULER_PROCESSOR_ID` | *(unset)* | No | Unique identifier for this worker instance. Useful for multi-instance deployability and observability. | +| `RETRY_SCHEDULER_BATCH_SIZE` | `10` | No | Number of retry jobs to process per tick. | + +### 2.12 Scheduled notification scheduler + +| Variable | Default | Required | Description | +|---|---|---|---| +| `SCHEDULER_ENABLED` | `true` | No | Enable the scheduled notification dispatcher. Set to `false` to disable. | +| `SCHEDULER_POLL_INTERVAL_MS` | `10000` | No | How often the scheduler polls for due notifications (ms). | +| `SCHEDULER_LOCK_TIMEOUT_MS` | `60000` | No | Worker lock timeout (ms). | +| `SCHEDULER_PROCESSOR_ID` | *(unset)* | No | Unique identifier for this scheduler worker instance. | +| `SCHEDULER_BATCH_SIZE` | `10` | No | Due notifications to dispatch per tick. | +| `SCHEDULER_TIMING_BUFFER_MS` | `60000` | No | Timing buffer (ms) applied around scheduled times to prevent edge-case early or late dispatch. | + +### 2.13 Event processing queue + +| Variable | Default | Required | Description | +|---|---|---|---| +| `EVENT_QUEUE_MAX_CONCURRENCY` | `1` | No | Maximum number of contract events to process concurrently. | +| `EVENT_QUEUE_MAX_RETRIES` | `3` | No | Maximum retries per event before permanent failure. | +| `EVENT_QUEUE_BASE_DELAY_MS` | `2000` | No | Base delay for event queue retry backoff (ms). | +| `EVENT_QUEUE_POLL_INTERVAL_MS` | `1000` | No | How often the event queue checks for events ready to process (ms). | + +### 2.14 Rate limiting + +| Variable | Default | Required | Description | +|---|---|---|---| +| `RATE_LIMIT_ENABLED` | `true` | No | Enable rate limiting on the events API. Set to `false` to disable. | +| `RATE_LIMIT_WINDOW_MS` | `60000` | No | Rate limit sliding window size (ms). | +| `RATE_LIMIT_MAX_REQUESTS` | `60` | No | Maximum requests allowed per window (applies to all clients unless overridden). | +| `RATE_LIMIT_CLIENT_OVERRIDES` | `{}` | No | Per-client rate limit overrides. JSON object. See shape below. | + +**`RATE_LIMIT_CLIENT_OVERRIDES` shape:** ```json { "dashboard-dev": { "maxRequests": 120, "windowMs": 60000 }, @@ -151,61 +189,145 @@ Used for retrying failed deliveries/processing. } ``` -### 2.12 Event queue / concurrency +### 2.15 Cleanup / retention policies + +| Variable | Default | Required | Description | +|---|---|---|---| +| `CLEANUP_INTERVAL_MS` | `3600000` | No | How often the cleanup job runs (ms). Default: 1 hour. | +| `NOTIFICATION_RETENTION_MS` | `604800000` | No | How long processed notifications are retained (ms). Default: 7 days. | +| `RATE_LIMIT_EVENT_RETENTION_MS` | `86400000` | No | How long rate-limit tracking records are retained (ms). Default: 24 hours. | +| `EVENT_RETENTION_MS` | `86400000` | No | How long raw contract events are retained (ms). Default: 24 hours. | +| `EXECUTION_LOG_RETENTION_MS` | `7776000000` | No | How long execution log entries are retained (ms). Default: 90 days. | + +### 2.16 Notification archive + +The archiver moves old completed/failed/cancelled notifications to a separate archive table, then permanently deletes them after a further retention period. + +| Variable | Default | Required | Description | +|---|---|---|---| +| `ARCHIVE_ENABLED` | `true` | No | Enable the background archiver. Set to `false` to disable. | +| `ARCHIVE_INTERVAL_MS` | `21600000` | No | How often the archive cycle runs (ms). Default: 6 hours. | +| `ARCHIVE_AFTER_MS` | `604800000` | No | Archive notifications completed more than this many ms ago (ms). Default: 7 days. | +| `ARCHIVE_DELETE_AFTER_MS` | `7776000000` | No | Permanently delete archived records older than this many ms since archiving. Set to `0` to never delete. Default: 90 days. | +| `ARCHIVE_BATCH_SIZE` | `500` | No | Maximum rows moved per archive cycle. Prevents long-running transactions. | + +### 2.17 Analytics + +| Variable | Default | Required | Description | +|---|---|---|---| +| `ANALYTICS_ENABLED` | `true` | No | Enable the notification analytics aggregator. Set to `false` to disable. | +| `ANALYTICS_MAX_RECORDS` | `10000` | No | Maximum number of in-memory analytics records to retain. | +| `ANALYTICS_MAX_BUCKETS` | `168` | No | Maximum time buckets to keep in memory. Default 168 = 7 days of hourly buckets. | +| `ANALYTICS_BUCKET_SIZE_MS` | `3600000` | No | Size of each analytics time bucket (ms). Default: 1 hour. | +| `ANALYTICS_PERSIST_INTERVAL_MS` | `300000` | No | How often analytics data is flushed to the database (ms). Default: 5 minutes. | +| `ANALYTICS_SNAPSHOT_RETENTION_DAYS` | `30` | No | Days to retain analytics snapshots in the database. | + +--- + +## 3. Dashboard service variables -| Variable | Purpose | Format | Default | Notes | -|---|---|---|---|---| -| `EVENT_QUEUE_MAX_CONCURRENCY` | Max concurrent event-processing tasks | integer | `1` | | -| `EVENT_QUEUE_MAX_RETRIES` | Max retries for event queue tasks | integer | `3` | | -| `EVENT_QUEUE_BASE_DELAY_MS` | Base delay for event queue retry backoff | integer (ms) | `2000` | | -| `EVENT_QUEUE_POLL_INTERVAL_MS` | Event queue poll interval | integer (ms) | `1000` | | +These are Vite build-time variables. They are embedded into the static bundle at build time and are **not secret**. All variables must be prefixed with `VITE_`. -## 3. Sample configuration (copy/paste) +| Variable | Default | Required | Description | +|---|---|---|---| +| `VITE_EVENTS_API_URL` | `http://localhost:8787/api/events` | **Yes** (for non-local) | Full URL to the listener events API endpoint. The dashboard polls this to display contract events. | +| `VITE_STELLAR_NETWORK` | `TESTNET` | No | Stellar network identifier used for display and Stellar SDK context. Values: `TESTNET` \| `PUBLIC`. | +| `VITE_API_URL` | `http://localhost:3000/api` | No | Alternative API base URL for the template management endpoints. Only needed when templates are served from a different origin than the events API. | +| `VITE_INDEXING_HEALTH_URL` | *(derived)* | No | URL for the indexing service health check. If unset, automatically derived from `VITE_EVENTS_API_URL`. | +| `VITE_NOTIFICATION_HEALTH_URL` | *(derived)* | No | URL for the notification service health check. If unset, automatically derived from `VITE_EVENTS_API_URL`. | +| `VITE_ENV` | *(unset)* | No | Environment label for display or analytics (e.g. `development`, `staging`, `production`). | -Below are examples that match the parsing/validation behavior in `listener/src/config.ts`. +--- + +## 4. Frontend service variables + +These are Create React App build-time variables. All must be prefixed with `REACT_APP_`. + +| Variable | Default | Required | Description | +|---|---|---|---| +| `REACT_APP_PREFERENCE_CONTRACT_ID` | `""` | **Yes** | Deployed Soroban contract ID for the user preferences contract. Obtain after deploying the contract to your target network. | + +--- + +## 5. CI/CD secrets (GitHub Actions) + +These values are stored as [GitHub repository secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) and injected at CI/CD build time. They are never committed to the repository. + +| Secret | Workflow | Description | +|---|---|---| +| `PREVIEW_EVENTS_API_URL` | `preview.yml` | Injected as `VITE_EVENTS_API_URL` when building preview deployments for pull requests. | +| `CLOUDFLARE_API_TOKEN` | `preview.yml`, `preview-cleanup.yml` | Cloudflare API token with Pages write access, used by Wrangler to deploy the dashboard. | +| `CLOUDFLARE_ACCOUNT_ID` | `preview.yml`, `preview-cleanup.yml` | Cloudflare account ID for the Pages project. | + +To add or rotate these secrets: GitHub → Repository → Settings → Secrets and variables → Actions. + +--- + +## 6. Task Bounty contract deployment (Ethereum) -### 3.1 Local / development (single instance) +These variables are only needed when deploying the Ethereum bounty contracts under `Documents/Task Bounty/`. They are consumed by Hardhat deployment scripts via `dotenv`. + +| Variable | Example | Required | Description | +|---|---|---|---| +| `PRIVATE_KEY` | `0xabc123...` | **Yes** | Ethereum deployer private key. ⚠️ Secret — never commit. | +| `MAINNET_RPC_URL` | `https://eth-mainnet.g.alchemy.com/v2/` | Conditional | Ethereum mainnet RPC endpoint (e.g. Alchemy). Required for mainnet deployments. | +| `SEPOLIA_RPC_URL` | `https://eth-sepolia.g.alchemy.com/v2/` | Conditional | Ethereum Sepolia testnet RPC endpoint. Required for testnet deployments. | +| `ETHERSCAN_API_KEY` | `ABCDEF...` | No | Etherscan API key for post-deployment contract verification. | +| `BOUNTY_ADDRESS` | `0x...` | No | Deployed bounty contract address. Populate after deployment. | +| `RESOLVER_ADDRESS` | `0x...` | No | Deployed resolver contract address. Populate after deployment. | +| `FACTORY_ADDRESS` | `0x...` | No | Deployed factory contract address. Populate after deployment. | +| `ARBITRATOR` | `0x...` | No | Arbitrator address. Defaults to the deployer address if not set. | + +--- + +## 7. Sample .env files + +### 7.1 Listener — local development ```bash +# Logging +LOG_LEVEL=info +# NODE_ENV=production + # Stellar STELLAR_NETWORK=testnet STELLAR_RPC_URL=https://soroban-testnet.stellar.org:443 -STELLAR_NETWORK_PASSPHRASE='Test SDF Network ; September 2015' +STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 -# What to monitor (contracts + event names) -CONTRACT_ADDRESSES='[ - {"address":"123abc...","events":["TaskCreated","WorkSubmitted"]} -]' +# Contracts to monitor +CONTRACT_ADDRESSES='[{"address":"CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","events":["*"]}]' -# Listener HTTP API +# HTTP API EVENTS_API_PORT=8787 EVENTS_API_CORS_ORIGIN=http://localhost:5173 # Database DATABASE_PATH=./data/notifications.db -# Discord (optional) -# DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...' -# DISCORD_WEBHOOK_ID='...' +# Discord (optional — uncomment and fill both) +# DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN +# DISCORD_WEBHOOK_ID=YOUR_ID -# Webhook verification secrets (optional) -WEBHOOK_SECRETS='[ - {"id":"target-1","secret":"whsec_example_secret"} -]' +# Webhook secrets +WEBHOOK_SECRETS='[{"id":"default","secret":"whsec_your_secret_here"}]' -# Cleanup/retention -CLEANUP_INTERVAL_MS=3600000 -NOTIFICATION_RETENTION_MS=604800000 -RATE_LIMIT_EVENT_RETENTION_MS=86400000 -EVENT_RETENTION_MS=86400000 +# Payload integrity (optional) +# PAYLOAD_INTEGRITY_SECRET=your_hmac_secret_here -# Scheduler -SCHEDULER_ENABLED=true -SCHEDULER_POLL_INTERVAL_MS=10000 -SCHEDULER_LOCK_TIMEOUT_MS=60000 -SCHEDULER_PROCESSOR_ID=local-1 -SCHEDULER_BATCH_SIZE=10 -SCHEDULER_TIMING_BUFFER_MS=60000 +# API keys (optional) +# API_KEYS='[{"key":"sk_dev_abc123","name":"local-dashboard"}]' + +# Polling +POLL_INTERVAL_MS=30000 +MAX_RECONNECT_ATTEMPTS=5 +RECONNECT_DELAY_MS=5000 + +# Retry queue +RETRY_BASE_DELAY_MS=5000 +RETRY_MAX_RETRIES=5 +RETRY_MULTIPLIER=2 +RETRY_MAX_DELAY_MS=3600000 +RETRY_JITTER=true # Retry scheduler RETRY_SCHEDULER_ENABLED=true @@ -213,124 +335,162 @@ RETRY_SCHEDULER_POLL_INTERVAL_MS=15000 RETRY_SCHEDULER_LOCK_TIMEOUT_MS=60000 RETRY_SCHEDULER_PROCESSOR_ID=local-1 RETRY_SCHEDULER_BATCH_SIZE=10 -RETRY_BASE_DELAY_MS=5000 -RETRY_MAX_RETRIES=5 -RETRY_MULTIPLIER=2 -RETRY_JITTER=true -RETRY_MAX_DELAY_MS=3600000 + +# Scheduled notifications +SCHEDULER_ENABLED=true +SCHEDULER_POLL_INTERVAL_MS=10000 +SCHEDULER_LOCK_TIMEOUT_MS=60000 +SCHEDULER_PROCESSOR_ID=local-1 +SCHEDULER_BATCH_SIZE=10 +SCHEDULER_TIMING_BUFFER_MS=60000 # Rate limiting RATE_LIMIT_ENABLED=true RATE_LIMIT_WINDOW_MS=60000 RATE_LIMIT_MAX_REQUESTS=60 -RATE_LIMIT_CLIENT_OVERRIDES='{}' - -# Event queue -EVENT_QUEUE_MAX_CONCURRENCY=1 -EVENT_QUEUE_MAX_RETRIES=3 -EVENT_QUEUE_BASE_DELAY_MS=2000 -EVENT_QUEUE_POLL_INTERVAL_MS=1000 +RATE_LIMIT_CLIENT_OVERRIDES={} ``` -### 3.2 Production-like (multi-instance safe practices) - -Key differences: -- Set explicit `*_PROCESSOR_ID` per instance for observability. -- Use stable storage for `DATABASE_PATH`. -- Use your real webhook secrets and Discord webhook values. +### 7.2 Listener — production (key differences highlighted) ```bash +# Logging +LOG_LEVEL=warn +NODE_ENV=production + +# Stellar mainnet STELLAR_NETWORK=public -STELLAR_RPC_URL=https://your-rpc.example.com -STELLAR_NETWORK_PASSPHRASE='Public Global Stellar Network ; September 2015' +STELLAR_RPC_URL=https://soroban-mainnet.stellar.org:443 +STELLAR_NETWORK_PASSPHRASE=Public Global Stellar Network ; September 2015 -CONTRACT_ADDRESSES='[ - {"address":"","events":["TaskCreated","WorkSubmitted","SubmissionApproved"]} -]' +# Production contracts +CONTRACT_ADDRESSES='[{"address":"","events":["TaskCreated","WorkSubmitted"]}]' +# HTTP API — set exact dashboard origin EVENTS_API_PORT=8787 EVENTS_API_CORS_ORIGIN=https://your-dashboard.example.com +# Persistent storage path DATABASE_PATH=/var/lib/notifychain/notifications.db -# Discord (optional) -DISCORD_WEBHOOK_URL='https://discord.com/api/webhooks/...' -DISCORD_WEBHOOK_ID='...' +# Discord (inject from secrets manager) +DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... +DISCORD_WEBHOOK_ID=... -# Webhook secrets (optional) -WEBHOOK_SECRETS='[ - {"id":"","secret":""} -]' +# Webhook secrets (inject from secrets manager) +WEBHOOK_SECRETS='[{"id":"prod-target","secret":""}]' +PAYLOAD_INTEGRITY_SECRET= -SCHEDULER_ENABLED=true -SCHEDULER_PROCESSOR_ID=worker-a +# API keys (inject from secrets manager) +API_KEYS='[{"key":"","name":"dashboard-prod"}]' -RETRY_SCHEDULER_ENABLED=true +# Worker identity (set per instance in multi-instance deployments) +SCHEDULER_PROCESSOR_ID=worker-a RETRY_SCHEDULER_PROCESSOR_ID=worker-a -RATE_LIMIT_ENABLED=true +# Tighter rate limits for production RATE_LIMIT_MAX_REQUESTS=60 -RATE_LIMIT_WINDOW_MS=60000 -RATE_LIMIT_CLIENT_OVERRIDES='{ - "dashboard": {"maxRequests": 120, "windowMs": 60000} -}' +RATE_LIMIT_CLIENT_OVERRIDES='{"dashboard":{"maxRequests":120,"windowMs":60000}}' +``` + +### 7.3 Dashboard — local development + +```bash +VITE_EVENTS_API_URL=http://localhost:8787/api/events +VITE_STELLAR_NETWORK=TESTNET +``` + +### 7.4 Dashboard — production + +```bash +VITE_EVENTS_API_URL=https://listener.your-domain.com/api/events +VITE_STELLAR_NETWORK=PUBLIC +VITE_ENV=production +``` + +### 7.5 Frontend — local development + +```bash +REACT_APP_PREFERENCE_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` -## 4. Security recommendations +--- + +## 8. Security recommendations + +### 8.1 Variables that must be treated as secrets + +Never commit these to version control. Inject them via a secrets manager or CI/CD secrets: -### 4.1 Treat these as secrets -The following must never be committed to git and should be injected via a secrets manager / CI/CD secrets: +| Variable | Service | +|---|---| +| `DISCORD_WEBHOOK_URL` | Listener | +| `DISCORD_WEBHOOK_ID` | Listener | +| `WEBHOOK_SECRETS` | Listener | +| `PAYLOAD_INTEGRITY_SECRET` | Listener | +| `API_KEYS` | Listener | +| `PRIVATE_KEY` | Task Bounty (Ethereum) | +| `MAINNET_RPC_URL` / `SEPOLIA_RPC_URL` | Task Bounty (contains API keys) | +| `ETHERSCAN_API_KEY` | Task Bounty | -- `DISCORD_WEBHOOK_URL` -- `DISCORD_WEBHOOK_ID` -- `WEBHOOK_SECRETS` (contains per-target secret material) +### 8.2 Use a secrets manager -### 4.2 Use a secrets manager -Use one of: +Prefer one of: - AWS Secrets Manager / SSM Parameter Store - GCP Secret Manager - Azure Key Vault - HashiCorp Vault -- Kubernetes Secrets (optionally backed by an external secrets operator) +- Kubernetes Secrets backed by an external secrets operator -Prefer short-lived credentials where possible. +### 8.3 Avoid logging secrets -### 4.3 Avoid logging secrets -- Do not print `process.env`. -- Ensure logger configuration does not include the raw secret env vars. -- When catching errors, avoid including full config objects in error messages. +- Do not log `process.env` in its entirety. +- Ensure error handlers do not serialize full config objects. +- When using structured logging, explicitly allowlist fields to log. -### 4.4 Rotate secrets safely -- Rotate `WEBHOOK_SECRETS` entries by `id`. -- Perform rotation with overlap (run new config while old secrets remain temporarily valid) if the verifier logic supports it. -- After rotation, restart listener instances to pick up new values. +### 8.4 Rotate secrets safely -### 4.5 Enforce allowlists / least privilege (webhook targets) -- Only include webhook IDs/secrets for targets you explicitly trust. -- Avoid broad “catch-all” secrets. +1. Add the new secret alongside the old one (overlap period). +2. Deploy the updated configuration. +3. Verify new secret is working. +4. Remove the old secret and redeploy. -### 4.6 Use correct JSON escaping -Because `CONTRACT_ADDRESSES`, `WEBHOOK_SECRETS`, and `RATE_LIMIT_CLIENT_OVERRIDES` are parsed as JSON strings: -- Ensure your shell quoting is correct. -- Prefer single quotes around the entire JSON string in bash-like shells. +For `WEBHOOK_SECRETS`, rotation is keyed by the `id` field — add new entries before removing old ones. -### 4.7 CORS hardening -If you expose the dashboard publicly: -- Set `EVENTS_API_CORS_ORIGIN` to exact origins. -- Avoid `*` in production. +### 8.5 CORS hardening -### 4.8 Do not commit `.env` files -This repo uses example env files (e.g. `.env.staging` exists). Ensure: -- `.env*` is in `.gitignore` (and verify it in CI). -- only example templates are committed. +Set `EVENTS_API_CORS_ORIGIN` to exact origins in production. Never use `*` if the API serves authenticated data. -## 5. Operational notes / troubleshooting +### 8.6 Do not commit .env files + +Only `.env.example` files (templates without real values) should be committed. Verify your `.gitignore` includes: +``` +.env +.env.local +.env.*.local +``` -- **Missing required/paired values**: `DISCORD_WEBHOOK_URL` and `DISCORD_WEBHOOK_ID` must be provided together. -- **JSON parse failures**: malformed `CONTRACT_ADDRESSES`, `WEBHOOK_SECRETS`, or `RATE_LIMIT_CLIENT_OVERRIDES` will cause a startup config error. -- **Scheduler & retry behavior**: ensure `*_LOCK_TIMEOUT_MS` and `*_POLL_INTERVAL_MS` suit your deployment latency and number of instances. +### 8.7 JSON-valued variables + +`CONTRACT_ADDRESSES`, `WEBHOOK_SECRETS`, `API_KEYS`, and `RATE_LIMIT_CLIENT_OVERRIDES` are parsed as JSON. Use single-quotes around the entire JSON string in bash-style shells to avoid escaping issues: +```bash +export CONTRACT_ADDRESSES='[{"address":"Cxxx","events":["*"]}]' +``` --- -*Last updated: 2026-06-26.* +## 9. Operational notes and troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| `DISCORD_WEBHOOK_URL is required when DISCORD_WEBHOOK_ID is provided` | One of the Discord pair is set but not the other | Provide both or neither | +| `CONTRACT_ADDRESSES must be valid JSON` | Malformed JSON string or shell quoting issue | Validate JSON with `echo $CONTRACT_ADDRESSES \| jq .` | +| `WEBHOOK_SECRETS must be a JSON array` | Same as above | Validate with `jq` | +| Events not appearing in dashboard | Wrong `VITE_EVENTS_API_URL` or CORS mismatch | Ensure `VITE_EVENTS_API_URL` matches the listener host and `EVENTS_API_CORS_ORIGIN` matches the dashboard origin | +| Scheduler not processing jobs | `SCHEDULER_ENABLED=false` or lock timeout too short | Set `SCHEDULER_ENABLED=true`, increase `SCHEDULER_LOCK_TIMEOUT_MS` | +| Duplicate notifications delivered | `NOTIFICATION_DEDUPLICATION_WINDOW_MS` too short | Increase the dedup window | +| `PORT` / `RPC_URL` in `listener/.env.staging` | These aliases do not map to `config.ts` variables | Use `EVENTS_API_PORT` and `STELLAR_RPC_URL` instead | + +--- +*Last updated: 2026-07-25. Source of truth: `listener/src/config.ts`, `listener/src/services/archive-config.ts`, dashboard source files in `dashboard/src/`, `frontend/src/services/preferenceService.ts`, and CI/CD workflow files in `.github/workflows/`.* diff --git a/dashboard/.env.example b/dashboard/.env.example index b74c776..558ed10 100644 --- a/dashboard/.env.example +++ b/dashboard/.env.example @@ -1,8 +1,47 @@ -# Frontend environment variables for dashboard -# Visit README or Vite docs for more info about VITE_ env variables. +# ============================================================================= +# NotifyChain — Dashboard Service Environment Variables (Vite) +# Copy this file to .env and fill in your values. +# All variables must be prefixed with VITE_ to be exposed to the browser bundle. +# ============================================================================= -# Backend listener API endpoint used by the dashboard to fetch events. +# ----------------------------------------------------------------------------- +# Listener API +# ----------------------------------------------------------------------------- + +# Full URL to the listener HTTP API events endpoint. +# The dashboard polls this to display contract events. +# Development default points to the local listener process (port 8787). VITE_EVENTS_API_URL=http://localhost:8787/api/events -# Stellar network to use: TESTNET or PUBLIC. +# Alternative API base URL used by the template management endpoints. +# Only required if the template API is served from a different origin than +# the events API. +# VITE_API_URL=http://localhost:3000/api + +# ----------------------------------------------------------------------------- +# Health Check URLs (optional — derived automatically if not set) +# ----------------------------------------------------------------------------- + +# URL for the indexing service health check endpoint. +# If unset, derived from VITE_EVENTS_API_URL by replacing /api/events with /health/indexing. +# VITE_INDEXING_HEALTH_URL=http://localhost:8787/health/indexing + +# URL for the notification service health check endpoint. +# If unset, derived from VITE_EVENTS_API_URL by replacing /api/events with /health/notifications. +# VITE_NOTIFICATION_HEALTH_URL=http://localhost:8787/health/notifications + +# ----------------------------------------------------------------------------- +# Stellar Network +# ----------------------------------------------------------------------------- + +# Stellar network to connect to. Used for display and Stellar SDK context. +# Values: TESTNET | PUBLIC VITE_STELLAR_NETWORK=TESTNET + +# ----------------------------------------------------------------------------- +# Environment Label (optional) +# ----------------------------------------------------------------------------- + +# Identifies the running environment. Useful for debug banners or analytics. +# Common values: development | staging | production +# VITE_ENV=development diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..448b299 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,14 @@ +# ============================================================================= +# NotifyChain — Frontend Service Environment Variables (Create React App / React) +# Copy this file to .env and fill in your values. +# All variables must be prefixed with REACT_APP_ to be exposed to the browser bundle. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Stellar Contract IDs +# ----------------------------------------------------------------------------- + +# Deployed Soroban contract ID for the user preferences contract. +# Obtain this after deploying the contract to your target network. +# Example (Testnet): CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +REACT_APP_PREFERENCE_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/listener/.env.example b/listener/.env.example index 1c63743..036b9c1 100644 --- a/listener/.env.example +++ b/listener/.env.example @@ -1,69 +1,281 @@ -# Logging Configuration +# ============================================================================= +# NotifyChain — Listener Service Environment Variables +# Copy this file to .env and fill in your values. +# Lines starting with # are comments. Lines starting with # followed by a +# variable name are optional/uncomment-to-enable settings. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- + +# Winston log level: error | warn | info | http | verbose | debug | silly LOG_LEVEL=info -# NODE_ENV=production # Uncomment for newline-delimited JSON log output -# Stellar Network Configuration +# Set to 'production' to enable newline-delimited JSON (structured) log output. +# Leave unset for human-readable pretty-print output during development. +# NODE_ENV=production + +# ----------------------------------------------------------------------------- +# Stellar / Chain Connectivity +# ----------------------------------------------------------------------------- + +# Stellar network label. Common values: testnet | public STELLAR_NETWORK=testnet + +# Soroban RPC endpoint for the target network. +# Testnet: https://soroban-testnet.stellar.org:443 +# Mainnet: https://soroban-mainnet.stellar.org:443 (or your own node) STELLAR_RPC_URL=https://soroban-testnet.stellar.org:443 + +# Network passphrase — must match the network above. +# Testnet: Test SDF Network ; September 2015 +# Mainnet: Public Global Stellar Network ; September 2015 STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 -# Contract Addresses to Monitor (JSON array) +# ----------------------------------------------------------------------------- +# Contract Addresses to Monitor +# ----------------------------------------------------------------------------- +# JSON array of objects: [{"address":"","events":["", ...]}] +# Use "*" as a wildcard event name to receive all events from a contract. +# Example: +# CONTRACT_ADDRESSES=[{"address":"CXXX...","events":["TaskCreated","WorkSubmitted"]}] CONTRACT_ADDRESSES=[{"address":"CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","events":["*"]}] -# Polling Configuration -POLL_INTERVAL_MS=30000 -MAX_RECONNECT_ATTEMPTS=5 -RECONNECT_DELAY_MS=5000 +# ----------------------------------------------------------------------------- +# Listener HTTP API Server +# ----------------------------------------------------------------------------- -# Events API Configuration +# Port the listener HTTP server listens on. EVENTS_API_PORT=8787 + +# Allowed CORS origin for the events API. Set to your dashboard URL in production. +# Avoid using '*' in production environments. EVENTS_API_CORS_ORIGIN=http://localhost:5173 + +# ----------------------------------------------------------------------------- +# Database +# ----------------------------------------------------------------------------- + +# Path to the SQLite database file used for deduplication, cursors, and scheduler state. +# Ensure this path is persistent and writable in your deployment environment. +DATABASE_PATH=./data/notifications.db + +# ----------------------------------------------------------------------------- +# Discord Delivery (optional — both must be provided together or neither) +# ⚠️ SECRETS: inject via a secrets manager, never commit actual values. +# ----------------------------------------------------------------------------- + +# Full Discord webhook URL (from Server Settings → Integrations → Webhooks). +# DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN + +# Discord webhook ID (the numeric ID portion of the webhook URL). +# DISCORD_WEBHOOK_ID=YOUR_WEBHOOK_ID + +# Number of delivery retries for Discord notifications (default: uses RETRY_MAX_RETRIES). +# DISCORD_RETRY_COUNT=3 + +# Base for exponential backoff on Discord delivery retries, in seconds (default: uses RETRY_BASE_DELAY_MS / 1000). +# DISCORD_BACKOFF_BASE_SECONDS=5 + +# Deduplication window for Discord notifications (ms). Prevents duplicate sends. +# NOTIFICATION_DEDUPLICATION_WINDOW_MS=60000 + +# Max entries held in the Discord deduplication cache. +# NOTIFICATION_DEDUPLICATION_MAX_SIZE=10000 + +# ----------------------------------------------------------------------------- +# Webhook Security +# ⚠️ SECRETS: inject via a secrets manager, never commit actual values. +# ----------------------------------------------------------------------------- + +# JSON array of {id, secret} objects used to sign/verify outgoing webhook payloads. +# Generate secrets with: openssl rand -base64 32 +# Example: [{"id":"target-1","secret":"whsec_your_secret_here"}] WEBHOOK_SECRETS=[{"id":"default","secret":"whsec_your_secret_here"}] -# Discord Webhook Configuration (optional) -DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN +# HMAC-SHA256 secret for scheduled notification payload integrity hashing. +# Generate with: openssl rand -base64 32 +# PAYLOAD_INTEGRITY_SECRET=your_hmac_secret_here + +# ----------------------------------------------------------------------------- +# API Keys (optional — for protecting the listener HTTP API) +# ⚠️ SECRETS: inject via a secrets manager, never commit actual values. +# ----------------------------------------------------------------------------- + +# JSON array of {key, name?} objects. Clients must supply a matching key. +# Example: [{"key":"sk_live_abc123","name":"dashboard-prod"}] +# API_KEYS=[] + +# ----------------------------------------------------------------------------- +# Polling / Reconnect +# ----------------------------------------------------------------------------- + +# How often to poll Stellar for new contract events (ms). +POLL_INTERVAL_MS=30000 + +# Maximum number of reconnect attempts when the RPC endpoint fails. +MAX_RECONNECT_ATTEMPTS=5 + +# Delay between reconnect attempts (ms). +RECONNECT_DELAY_MS=5000 -# Retry Queue Configuration +# ----------------------------------------------------------------------------- +# Retry Queue (in-memory, fast retries) +# ----------------------------------------------------------------------------- + +# Base delay for in-memory retry exponential backoff (ms). RETRY_BASE_DELAY_MS=5000 + +# Maximum number of in-memory retry attempts before permanent failure. RETRY_MAX_RETRIES=5 + +# Exponential backoff multiplier (delay = base * multiplier^attempt). RETRY_MULTIPLIER=2 + +# Maximum clamped retry delay (ms). RETRY_MAX_DELAY_MS=3600000 + +# Whether to add random jitter to retry delays (set to 'false' to disable). RETRY_JITTER=true -# Retry Scheduler (DB-backed retry for persisted notifications) +# How often the in-memory retry queue is processed (ms). +# RETRY_QUEUE_PROCESS_INTERVAL_MS=5000 + +# ----------------------------------------------------------------------------- +# Retry Scheduler (database-backed, persisted retries) +# ----------------------------------------------------------------------------- + +# Enable the database-backed retry scheduler. RETRY_SCHEDULER_ENABLED=true + +# How often the retry scheduler polls for due retry jobs (ms). RETRY_SCHEDULER_POLL_INTERVAL_MS=15000 + +# Lock timeout for the retry scheduler worker (ms). Prevents dual-processing. RETRY_SCHEDULER_LOCK_TIMEOUT_MS=60000 + +# Unique identifier for this retry scheduler worker instance. +# Useful for observability in multi-instance deployments. RETRY_SCHEDULER_PROCESSOR_ID= + +# Number of retry jobs to process per scheduler tick. RETRY_SCHEDULER_BATCH_SIZE=10 -# Database Configuration -DATABASE_PATH=./data/notifications.db +# ----------------------------------------------------------------------------- +# Scheduled Notification Scheduler +# ----------------------------------------------------------------------------- -# Scheduled Notification Scheduler Configuration +# Enable the scheduled notification dispatcher. SCHEDULER_ENABLED=true + +# How often the scheduler polls for due notifications (ms). SCHEDULER_POLL_INTERVAL_MS=10000 + +# Lock timeout for the scheduler worker (ms). SCHEDULER_LOCK_TIMEOUT_MS=60000 + +# Unique identifier for this scheduler worker instance. SCHEDULER_PROCESSOR_ID= + +# Number of due notifications to process per scheduler tick. SCHEDULER_BATCH_SIZE=10 + +# Timing buffer to avoid dispatching notifications too early or too late (ms). SCHEDULER_TIMING_BUFFER_MS=60000 -# Event Processing Queue Configuration -# EVENT_QUEUE_MAX_CONCURRENCY=1 # Max events to process concurrently -# EVENT_QUEUE_MAX_RETRIES=3 # Max retries per event before permanent failure -# EVENT_QUEUE_BASE_DELAY_MS=2000 # Base delay for exponential backoff (ms) -# EVENT_QUEUE_POLL_INTERVAL_MS=1000 # How often to check for due events (ms) +# ----------------------------------------------------------------------------- +# Event Processing Queue +# ----------------------------------------------------------------------------- + +# Maximum number of events to process concurrently. +# EVENT_QUEUE_MAX_CONCURRENCY=1 + +# Maximum retries per event before permanent failure. +# EVENT_QUEUE_MAX_RETRIES=3 + +# Base delay for event queue retry exponential backoff (ms). +# EVENT_QUEUE_BASE_DELAY_MS=2000 + +# How often the event queue checks for due events (ms). +# EVENT_QUEUE_POLL_INTERVAL_MS=1000 -# Rate Limiting Configuration +# ----------------------------------------------------------------------------- +# Rate Limiting +# ----------------------------------------------------------------------------- + +# Enable rate limiting on the events API (set to 'false' to disable). RATE_LIMIT_ENABLED=true + +# Rate limit window size (ms). RATE_LIMIT_WINDOW_MS=60000 + +# Maximum requests allowed per window (across all clients unless overridden). RATE_LIMIT_MAX_REQUESTS=60 -# Per-client overrides (JSON object): {"client-id":{"maxRequests":100,"windowMs":60000}} + +# Per-client rate limit overrides as a JSON object. +# Keys are client identifiers; values are {maxRequests, windowMs?}. +# Example: {"dashboard-dev":{"maxRequests":120,"windowMs":60000}} RATE_LIMIT_CLIENT_OVERRIDES={} -# Notification Archive Configuration -# ARCHIVE_ENABLED=true # Set to 'false' to disable the background archiver -# ARCHIVE_INTERVAL_MS=21600000 # How often to run the archive cycle (default: 6 h) -# ARCHIVE_AFTER_MS=604800000 # Archive notifications completed > X ms ago (default: 7 days) -# ARCHIVE_DELETE_AFTER_MS=7776000000 # Permanently delete archive rows > X ms old (default: 90 days; 0 = never) -# ARCHIVE_BATCH_SIZE=500 # Max rows processed per cycle +# ----------------------------------------------------------------------------- +# Cleanup / Retention Policies +# ----------------------------------------------------------------------------- + +# How often the cleanup job runs (ms). Default: 1 hour. +# CLEANUP_INTERVAL_MS=3600000 + +# How long processed notifications are retained before deletion (ms). Default: 7 days. +# NOTIFICATION_RETENTION_MS=604800000 + +# How long rate-limit tracking records are retained (ms). Default: 24 hours. +# RATE_LIMIT_EVENT_RETENTION_MS=86400000 + +# How long raw contract events are retained (ms). Default: 24 hours. +# EVENT_RETENTION_MS=86400000 + +# How long execution log entries are retained (ms). Default: 90 days. +# EXECUTION_LOG_RETENTION_MS=7776000000 + +# ----------------------------------------------------------------------------- +# Notification Archive +# ----------------------------------------------------------------------------- + +# Enable the background archiver that moves old notifications to an archive table. +# ARCHIVE_ENABLED=true + +# How often to run the archive cycle (ms). Default: 6 hours. +# ARCHIVE_INTERVAL_MS=21600000 + +# Move completed/failed/cancelled notifications to archive after this many ms +# since processing completed. Default: 7 days. +# ARCHIVE_AFTER_MS=604800000 + +# Permanently delete archived records after this many ms since archiving. +# Set to 0 to never delete. Default: 90 days. +# ARCHIVE_DELETE_AFTER_MS=7776000000 + +# Maximum rows processed per archive cycle (prevents long-running transactions). +# ARCHIVE_BATCH_SIZE=500 + +# ----------------------------------------------------------------------------- +# Analytics +# ----------------------------------------------------------------------------- + +# Enable the notification analytics aggregator (set to 'false' to disable). +# ANALYTICS_ENABLED=true + +# Maximum number of analytics records to retain in memory. +# ANALYTICS_MAX_RECORDS=10000 + +# Maximum number of time buckets to keep (default 168 = 7 days of hourly buckets). +# ANALYTICS_MAX_BUCKETS=168 + +# Size of each analytics time bucket (ms). Default: 1 hour. +# ANALYTICS_BUCKET_SIZE_MS=3600000 + +# How often analytics data is persisted to the database (ms). Default: 5 minutes. +# ANALYTICS_PERSIST_INTERVAL_MS=300000 + +# Number of days to retain analytics snapshots in the database. +# ANALYTICS_SNAPSHOT_RETENTION_DAYS=30