Repository navigation
Expand file tree
/
Copy path.env.example
More file actions
479 lines (441 loc) · 27.5 KB
/
Copy path.env.example
File metadata and controls
479 lines (441 loc) · 27.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# ─── Refract Backend configuration ─────────────────────────────────────────────
# Copy to `.env` and fill in. Never commit `.env` (it is gitignored).
# ── HTTP server ──────────────────────────────────────────────────────────────
# Port the API/WebSocket server listens on.
PORT=4001
# Comma-separated exact browser origins; no paths or wildcards. FRONTEND_URL
# remains supported as a single-origin fallback for existing deployments.
FRONTEND_URLS=http://localhost:3000
FRONTEND_URL=http://localhost:3000
# Required by endpoints marked with @AdminOnly. Send as X-API-Key.
ADMIN_API_KEY=
# ── Database (PostgreSQL 15+) ────────────────────────────────────────────────
# Apply src/db/schema.sql before first run: psql "$DATABASE_URL" -f src/db/schema.sql
DATABASE_URL=postgres://refract:refract@localhost:5432/refract
# Connection pool tuning (all optional — defaults shown).
# DB_POOL_MAX=10 # max simultaneous connections
# DB_POOL_MIN=2 # min idle connections kept alive
# DB_POOL_IDLE_TIMEOUT_MS=30000 # close idle connections after this
# DB_POOL_ACQUIRE_TIMEOUT_MS=5000 # throw if no free connection within this
# DB_STATEMENT_TIMEOUT_MS=30000 # hard cap per query (statement_timeout GUC)
# DB_CONNECT_RETRIES=5 # startup probe retry limit
# DB_CONNECT_RETRY_DELAY_MS=1000 # base delay between retries (doubles each attempt)
# ── Cache / pub-sub (Redis) ──────────────────────────────────────────────────
REDIS_URL=redis://localhost:6379
# ── Stellar / Soroban ────────────────────────────────────────────────────────
STELLAR_NETWORK=testnet
# RPC URL, Horizon URL, and passphrase are selected together by STELLAR_NETWORK.
# Set contract IDs for the selected network only; keep values from that
# network's refract-contracts deployment output.
#
# Startup fails unless everything below belongs to the one selected network:
# - SOROBAN_RPC_URL, SOROBAN_RPC_URLS, STELLAR_HORIZON_URL,
# STELLAR_NETWORK_PASSPHRASE, and the REFRACT_*_CONTRACT_ID values
# - each may use a network suffix (e.g. STELLAR_HORIZON_URL_TESTNET); a
# suffix for any other network is an error
# - unsuffixed and suffixed values for the same setting must match
# With STELLAR_VALIDATE_ONCHAIN=true, the RPC and Horizon endpoints must also
# report the selected network's passphrase. Run the same checks before
# deploying with: npx ts-node scripts/check-network-config.ts --live
# STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Optional comma-separated extra Soroban RPC endpoints, in preference order.
# Together with SOROBAN_RPC_URL they form a health-checked pool used for reads,
# event polling, and oracle publish submissions. Endpoints that fail health
# probes, fail calls repeatedly, or fall behind the head ledger are rotated out
# automatically and retried after a cooldown.
# SOROBAN_RPC_URLS=https://rpc-one.example,https://rpc-two.example
# SOROBAN_RPC_HEALTH_INTERVAL_MS=15000 # background getHealth probe (0 = off)
# SOROBAN_RPC_HEALTH_TIMEOUT_MS=5000
# SOROBAN_RPC_MAX_LEDGER_LAG=10 # ledgers behind head before "lagging"
# SOROBAN_RPC_FAILURE_THRESHOLD=3 # consecutive call failures before "unhealthy"
# SOROBAN_RPC_COOLDOWN_MS=30000 # how long an unhealthy endpoint is skipped
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
# Ledger at which event ingestion should begin when no checkpoint exists.
# Choose a ledger available from the configured RPC endpoint.
SOROBAN_EVENT_START_LEDGER=1
# Deployed contract IDs (see refract-contracts deploy output).
REFRACT_POOL_CONTRACT_ID_TESTNET=
REFRACT_POLICY_CONTRACT_ID_TESTNET=
REFRACT_ORACLE_CONTRACT_ID_TESTNET=
# REFRACT_POOL_CONTRACT_ID_FUTURENET=
# REFRACT_POLICY_CONTRACT_ID_FUTURENET=
# REFRACT_ORACLE_CONTRACT_ID_FUTURENET=
# REFRACT_POOL_CONTRACT_ID_MAINNET=
# REFRACT_POLICY_CONTRACT_ID_MAINNET=
# REFRACT_ORACLE_CONTRACT_ID_MAINNET=
# Relayer/admin secret used to push oracle readings on-chain and settle claims.
# Rotate via a rolling deployment: new replicas use this key while old replicas
# drain in-flight settlements. Keep the old account funded until its submitted
# work drains; PostgreSQL advisory locks serialize sequence use across replicas.
# Use a dedicated, low-value testnet key. NEVER use a mainnet key here.
#
# Prefer sourcing this from AWS Secrets Manager rather than an environment
# variable: grant the runtime IAM role access to the secret and set
# ORACLE_RELAYER_SECRET_ID instead of ORACLE_RELAYER_SECRET.
ORACLE_RELAYER_SECRET=
# AWS Secrets Manager secret containing the relayer's Stellar secret key.
# Grant the runtime IAM role access to this secret; never put the signing key
# in an environment variable. Use a dedicated, low-value testnet key.
ORACLE_RELAYER_SECRET_ID=
# Dedicated Ed25519 seed used to sign oracle decision attestations. Generate
# with `openssl rand -base64 32`; keep it separate from the Stellar relayer key.
ORACLE_ATTESTATION_PRIVATE_KEY=
# Multi-key relayer keystore: a JSON file (mode 0600, mounted from a secret
# manager) with a "keys" array of { id, secret, network, active } entries.
# Re-read when it changes, so rotation/revocation needs no restart. Takes
# precedence over ORACLE_RELAYER_SECRET (dev default only). Never logged.
# See docs/relayer-key-compromise-runbook.md.
# ORACLE_RELAYER_KEYSTORE_PATH=/run/secrets/relayer-keystore.json
# Oracle publish quorum (N-of-M independent submitter signatures).
# ORACLE_QUORUM_PAYOUT_THRESHOLD=2 # payout-triggering feeds (default 2)
# ORACLE_QUORUM_DEFAULT_THRESHOLD=1 # feeds listed in NON_PAYOUT_FEEDS
# ORACLE_QUORUM_NON_PAYOUT_FEEDS=
# ORACLE_QUORUM_THRESHOLDS=FlightDelay=3 # per-feed override
# Startup fails if a threshold exceeds active keys or the on-chain required_signers().
# Defaults to AWS_REGION/AWS_DEFAULT_REGION from the AWS runtime environment.
# AWS_REGION=
# Bootstrap validation (fail-fast vs degraded). Defaults are safe for local/test.
# STELLAR_VALIDATE_ONCHAIN=true
# STELLAR_REQUIRE_NETWORK=false
# STELLAR_VALIDATE_TIMEOUT_MS=5000
# Relayer sequence / fee controls (shared by claim settlement + oracle publish).
# RELAYER_FEE_MULTIPLIER=1.2
# RELAYER_FEE_CEILING=1000000
# RELAYER_IDLE_RESYNC_MS=30000
# RELAYER_MIN_BALANCE=50000000
# RELAYER_MAX_ATTEMPTS=3
# ── Soroban event indexer ────────────────────────────────────────────────────
# Polls getEvents for pool/policy/oracle contracts and projects into Postgres.
# Apply migrations/001_event_indexer.sql (or the indexer tables in schema.sql).
#
# IMPORTANT: Only ONE replica should run the indexer (leader election). Set
# EVENT_INDEXER_ENABLED=false on followers to avoid competing cursor writers.
# EVENT_INDEXER_ENABLED=true
# EVENT_INDEXER_POLL_INTERVAL_MS=5000
# EVENT_INDEXER_LAG_ALERT_LEDGERS=100
# EVENT_INDEXER_PAGE_LIMIT=100
# ── Confirmation polling (exponential backoff) ──────────────────────────────
# CONFIRMATION_INITIAL_INTERVAL_MS=400
# CONFIRMATION_BACKOFF_MULTIPLIER=1.8
# CONFIRMATION_MAX_INTERVAL_MS=8000
# CONFIRMATION_DEADLINE_MS=30000
# CONFIRMATION_JITTER_RATIO=0.2
# CONFIRMATION_SETTLEMENT_DEADLINE_MS=90000
# CONFIRMATION_HTTP_DEADLINE_MS=20000
# ── Oracle feed / quorum config ──────────────────────────────────────────────
# JSON feed definitions (sources, trust mode, quorum, poll and freshness
# windows). See docs/oracle-feeds.example.json. Set at most one of these; with
# neither, the built-in single-source feeds are used, which the validator
# rejects on mainnet. The config is validated at startup and the process
# refuses to start on any error. Lint it in CI with:
# npx ts-node scripts/oracle-config-lint.ts --file <path> --network mainnet
# ORACLE_FEEDS_FILE=/etc/refract/oracle-feeds.json
# ORACLE_FEEDS_JSON=
# Treat validator warnings as fatal at startup too.
# ORACLE_CONFIG_STRICT=false
# ── Oracle on-chain publish policy ───────────────────────────────────────────
# ORACLE_PUBLISH_MODE=on_change # or every_poll
# ORACLE_PUBLISH_MIN_INTERVAL_MS=300000
# ── Oracle trust modes (staged rollout) ──────────────────────────────────────
# Per-coverage trust mode: single_source (default) | corroborated | decentralized.
# Coverage suffixes: STABLECOIN_DEPEG, MARKET_CRASH, LIQUIDATION_SHIELD,
# SMART_CONTRACT_RISK, FLIGHT_DELAY. Unknown suffixes or modes fail at startup.
# ORACLE_TRUST_MODE_STABLECOIN_DEPEG=single_source
# ORACLE_TRUST_MODE_MARKET_CRASH=corroborated
# Distinct sources (corroborated) or registered provider identities
# (decentralized), comma-separated. Required for non-default modes.
# ORACLE_SOURCES_MARKET_CRASH=coingecko,defillama,horizon
# Minimum distinct agreeing sources for a non-default mode (integer >= 2).
# ORACLE_QUORUM_MIN=2
# ── Oracle leader election (Redis lease) ─────────────────────────────────────
# Only the replica holding the lease polls providers, publishes to RefractOracle,
# and settles claims. Followers stay up for API/reads. Uses REDIS_URL.
# Set false only for a single replica; disabled mode always reports leader.
# ORACLE_LEADER_ELECTION=true
# Lease TTL: how long a crashed leader blocks failover (ms).
# ORACLE_LEASE_TTL_MS=15000
# Renew/acquire cadence; must be less than ORACLE_LEASE_TTL_MS (ms).
# ORACLE_LEASE_RENEW_MS=5000
# Failure semantics: if Redis is unreachable or renewal fails, the leader steps
# down and publishing/settlement stop. The instance keeps serving reads and API
# traffic, logs a critical alert, and `refract_oracle_leader` drops to 0.
# Pending buy_policy records expire if the user never submits (ms).
# POLICY_PENDING_TTL_MS=3600000
# ── External oracle data sources ─────────────────────────────────────────────
# CoinGecko, Stellar Horizon, and DeFiLlama are all used for real and need
# no API key for this project's request volume. Horizon is selected with
# STELLAR_NETWORK. The other overrides below are optional.
# COINGECKO_BASE_URL=https://api.coingecko.com/api/v3
# DEFILLAMA_BASE_URL=https://api.llama.fi
# Protocol slug DeFiLlama's /protocol/:slug endpoint is polled for the
# SmartContractRisk TVL-drop check — a placeholder "covered protocol"
# until Refract defines a real list.
# DEFILLAMA_PROTOCOL_SLUG=aave
# Independent secondary providers used for corroboration (see
# docs/oracle-sources.md). Kraken (price) is keyless and on by default; Token
# Terminal (TVL) is only registered when a key is set. Supply the key through
# your secret manager / environment, never commit it.
# KRAKEN_ENABLED=true
# KRAKEN_BASE_URL=https://api.kraken.com
# TOKENTERMINAL_BASE_URL=https://api.tokenterminal.com
# TOKENTERMINAL_API_KEY=
# Timeout (ms) for each oracle HTTP call before it degrades to a safe,
# non-triggering reading (see OracleService.degraded()).
# ORACLE_HTTP_TIMEOUT_MS=5000
# Registered third-party data providers allowed to submit signed readings to
# POST /api/v1/oracle/submissions (see docs/oracle-submissions.md). JSON array.
# ORACLE_SUBMISSION_PROVIDERS=[{"id":"acme-feeds","publicKey":"<base64 raw Ed25519 key>","coverageTypes":["StablecoinDepeg"]}]
# ── Provider canary probes (src/services/providerProbe.ts) ───────────────────
# Out-of-band probes of each data provider. Results never reach on-chain
# publishing or corroboration. Keep the interval long enough that canary
# traffic stays small next to real polling (shared provider rate limits).
# PROVIDER_PROBE_INTERVAL_SECONDS=300
# PROVIDER_PROBE_JITTER_SECONDS=30
# PROVIDER_PROBE_TIMEOUT_MS=5000
# Identical payloads in this many consecutive probes count as a frozen feed.
# PROVIDER_PROBE_FROZEN_WINDOW=3
# Consecutive provider failures before the source is quarantined and alerted.
# PROVIDER_PROBE_QUARANTINE_AFTER=3
# Oracle confidence → quote availability and premium (src/quote/oracle-confidence.ts).
# Score is the product of source-count, independence, freshness, and divergence
# factors. Below UNAVAILABLE_BELOW the quote is refused; below DEGRADED_BELOW it
# is priced with a warning. The premium multiplier scales with (1 - score) and is
# capped at MAX_PREMIUM_MULTIPLIER, so pricing stays within regulator-defensible bounds.
# ORACLE_CONFIDENCE_MIN_SOURCES=2
# ORACLE_CONFIDENCE_MIN_INDEPENDENT_CLUSTERS=2
# ORACLE_CONFIDENCE_MAX_FRESHNESS_SECONDS=300
# ORACLE_CONFIDENCE_DIVERGENCE_PENALTY=0.5
# ORACLE_CONFIDENCE_DEGRADED_BELOW=0.8
# ORACLE_CONFIDENCE_UNAVAILABLE_BELOW=0.5
# ORACLE_CONFIDENCE_MAX_PREMIUM_MULTIPLIER=1.5
# ── Soroban RPC client (shared SorobanRpcService) ────────────────────────────
# SOROBAN_RPC_TIMEOUT_MS=10000
# SOROBAN_RPC_MAX_RETRIES=3
# Max stroops the relayer will pay for a RestoreFootprint (griefing guard).
# SOROBAN_RESTORE_FEE_CEILING=5000000
# SOROBAN_RESTORE_MAX_ATTEMPTS=3
# Proactive TTL extension (testnet/mainnet thresholds differ — keep config-driven).
# SOROBAN_TTL_EXTENSION_LEDGERS=17280
# SOROBAN_PROACTIVE_TTL_EXTEND_DAYS=30
# ── Claim scanner ────────────────────────────────────────────────────────────
# Max parallel oracle/evaluate workers per scan (settlement stays serial).
# CLAIM_SCAN_CONCURRENCY=8
# Allowed |actual-expected| payout delta before discrepancy alert (default exact).
# CLAIM_PAYOUT_MISMATCH_TOLERANCE=0
# ── FlightDelay data source (issue #181) ────────────────────────────────────
# Unset FLIGHT_DELAY_PROVIDER to disable FlightDelay readings entirely.
# FLIGHT_DELAY_PROVIDER=aviationstack
# Required when FLIGHT_DELAY_PROVIDER=aviationstack (free-tier key). Startup fails
# fast if it is missing. Never logged.
# AVIATIONSTACK_API_KEY=
# "paid-stub" selects a placeholder for a future paid provider and always errors.
# ── Fee strategy (issue #46) ─────────────────────────────────────────────────
# Hard ceiling on inclusion+resource fee in stroops (safety control).
# FEE_CEILING_STROOPS=10000000
# FEE_STATS_TTL_MS=15000
# FEE_MODERATE_PERCENTILE=90
# FEE_MODERATE_MULTIPLIER=1.2
# FEE_AGGRESSIVE_PERCENTILE=99
# FEE_AGGRESSIVE_MULTIPLIER=1.5
# ── Settlement retry / dead-letter (issue #48) ───────────────────────────────
# SETTLEMENT_MAX_ATTEMPTS=10
# SETTLEMENT_MAX_AGE_MS=86400000
# SETTLEMENT_BACKOFF_BASE_MS=300000
# SETTLEMENT_FAILURE_RATE_ALERT=0.5
# ── Attack-cost model / bond & quorum sizing (issue #288) ────────────────────
# Analytical model (scripts/attack-cost) estimates the cost of corrupting a
# feed's consensus via source bribery, N-source collusion, or K operator-key
# compromise, and derives the minimum bond and quorum thresholds required for
# the exposure a feed supports. The model version is recorded alongside the
# policy it validates.
#
# Safety factor: startup validation refuses to start when a payout-triggering
# feed's maximum exposure exceeds (modeled attack cost / safety factor).
# Higher = stricter. Must be >= 1.
# ATTACK_COST_SAFETY_FACTOR=10
# Model version pinned for validation; bump together with the model code.
# ATTACK_COST_MODEL_VERSION=1
# Default source independence weight for sources with unknown independence.
# Unknown independence is treated as fully dependent (worst case) = 0.
# ATTACK_COST_UNKNOWN_INDEPENDENCE_WEIGHT=0
# When true, feeds with dynamic exposure (e.g. pool TVL) are re-evaluated
# against the model on each exposure change; when false only at startup.
# ATTACK_COST_DYNAMIC_EXPOSURE_CHECK=true
# Fail startup if any payout-triggering feed cannot be modeled (missing bond
# or quorum policy). Set false only for local/dev environments.
# ATTACK_COST_REQUIRE_MODEL=true
# ── Feed calendar / market-hours awareness (issue #291) ──────────────────────
# Feeds may declare expected active windows (market hours, exchange holidays,
# IANA time zone, scheduled quiet periods). Freshness, staleness, and
# plausibility checks are evaluated against the calendar instead of assuming
# continuous 24/7 data. DST transitions are handled by the resolver.
#
# Default IANA time zone for feeds that do not declare one.
# FEED_CALENDAR_DEFAULT_TIMEZONE=UTC
# Holiday calendar source identifier (data + license declared separately).
# FEED_CALENDAR_HOLIDAY_SOURCE=
# Fail-safe behavior when a calendar cannot be resolved at startup:
# closed — treat feed as out-of-window (no staleness penalties) [default]
# open — treat feed as expected live (may trip breakers)
# error — refuse to start
# Never silently default to "open".
# FEED_CALENDAR_UNAVAILABLE_BEHAVIOR=closed
# Bounded grace period (ms) after an expected close before an unscheduled-halt
# alert fires, so unexpected closures still page operators.
# FEED_CALENDAR_UNEXPECTED_CLOSURE_GRACE_MS=900000
# TTL (ms) for the cached calendar resolver to avoid per-reading overhead.
# FEED_CALENDAR_CACHE_TTL_MS=60000
# When true, plausibility/anomaly checks are suspended during declared quiet
# periods; the suspension is recorded in evidence and metric labels.
# FEED_CALENDAR_SUSPEND_PLAUSIBILITY_IN_QUIET=true
# ── TEE-attested provider readings (issue #293) ──────────────────────────────
# Optional hardware attestation (e.g. AWS Nitro Enclave, SGX) bound to a
# submitted reading. Quotes are verified against a pinned vendor root, the
# nonce is bound to the request, the payload hash is matched against the
# canonicalized reading, and the measurement must be allowlisted. Attested
# readings get a higher attestation tier; unattested readings remain valid at
# the base tier. Attestation failures are recorded as evidence, never
# silently downgraded.
#
# Master switch. When false, attestation objects are ignored (base tier only).
# TEE_ATTESTATION_ENABLED=false
# Comma-separated TEE types to accept: nitro, sgx.
# TEE_ATTESTATION_TYPES=nitro
# Pinned vendor root certificate (PEM path or inline base64) per TEE type.
# TEE_NITRO_ROOT_CERT_PATH=
# TEE_SGX_ROOT_CERT_PATH=
# Allowlisted enclave measurements (comma-separated hex PCR/measurement
# values). Rotation after a legitimate enclave upgrade is an admin action,
# not a code change. Empty = reject all attested readings.
# TEE_ALLOWED_MEASUREMENTS=
# Max accepted quote size in bytes (oversized quotes are rejected).
# TEE_MAX_QUOTE_BYTES=65536
# Max clock skew (ms) tolerated when validating quote validity windows.
# TEE_QUOTE_CLOCK_SKEW_MS=60000
# Nonce TTL (ms) for replay protection; nonces are single-use.
# TEE_NONCE_TTL_MS=300000
# Timeout (ms) for vendor root/CRL endpoint fetches; on failure attestation
# fails closed (reading stays at base tier, failure recorded as evidence).
# TEE_VENDOR_FETCH_TIMEOUT_MS=5000
# Policy version recorded with each attestation weighting decision.
# TEE_ATTESTATION_POLICY_VERSION=1
# ── Ops API ──────────────────────────────────────────────────────────────────
# Bearer token for GET/POST /api/v1/claims/ops/* endpoints.
# OPS_API_TOKEN=
# ── Alert routing (dedup / grouping / escalation) ────────────────────────────
# Incidents are fingerprinted by feed ID + alert class + source (volatile
# fields excluded). Duplicate emits inside the dedup window collapse into the
# existing incident instead of paging again.
# ALERT_DEDUP_WINDOW_MS=300000
# Alerts for the same feed arriving inside this window are grouped into a
# single incident (one page per feed outage, not one per rule).
# ALERT_GROUP_WINDOW_MS=60000
# Delay before an unacknowledged page escalates to the secondary on-call.
# ALERT_ESCALATION_DELAY_MS=900000
# Re-notification cadence for an acknowledged-but-unresolved incident.
# ALERT_RENOTIFY_INTERVAL_MS=1800000
# Notifier adapter: log | memory | webhook. Delivery is pluggable; routing
# failures are swallowed so ingestion/settlement is never blocked.
# ALERT_NOTIFIER=log
# Required when ALERT_NOTIFIER=webhook. Receives incident payloads only —
# never secrets.
# ALERT_WEBHOOK_URL=
# ── Dead-man's-switch heartbeat + local watchdog ─────────────────────────────
# Heartbeat is emitted only after a *successful* poll cycle and DB write, so a
# wedged loop stops heartbeating by construction. Disable for local dev.
# HEARTBEAT_ENABLED=false
# External dead-man's-switch endpoint. Must not receive secrets.
# HEARTBEAT_URL=
# Expected heartbeat cadence (ms). Configure the external monitor's grace
# period to be at least HEARTBEAT_GRACE_PERIOD_MS.
# HEARTBEAT_INTERVAL_MS=60000
# Grace period (ms) the external monitor allows before declaring the
# heartbeat missing. Documented in the "heartbeat missing" runbook entry.
# HEARTBEAT_GRACE_PERIOD_MS=180000
# Consecutive failed sends required before paging (absorbs transient network
# errors to the heartbeat receiver).
# HEARTBEAT_MAX_CONSECUTIVE_MISSES=3
# Local watchdog: raise a high-severity alert when metrics scrapes are older
# than this (ms).
# WATCHDOG_METRICS_STALENESS_MS=300000
# Local watchdog: raise a high-severity alert when no alert delivery has
# succeeded within this window (ms).
# WATCHDOG_ALERT_DELIVERY_STALENESS_MS=600000
# ── Manual oracle override previews ──────────────────────────────────────────
# A preview token is valid for this long (ms); applying an override requires a
# preview generated within this window so the applied action matches review.
# OVERRIDE_PREVIEW_VALIDITY_MS=300000
# Server-side exposure ceiling (payout units) for a single override. Overrides
# exceeding it are rejected unless an additional approval is supplied. This is
# enforced in the API, not only in the operator CLI.
# OVERRIDE_EXPOSURE_CEILING=1000000
# Roles permitted to generate a preview (comma-separated).
# OVERRIDE_PREVIEW_ROLES=operator
# Roles permitted to apply an override (comma-separated). Must be a subset of
# OVERRIDE_PREVIEW_ROLES; apply always requires a matching preview id.
# OVERRIDE_APPLY_ROLES=operator,admin
# ── Oracle operator API (/api/v1/oracle/admin/*) ─────────────────────────────
# JSON array of operator credentials. Only the SHA-256 of each secret is stored.
# Clients send "Authorization: Bearer <keyId>.<secret>". Roles: oracle:viewer,
# oracle:operator, oracle:admin. expiresAt and disabled are optional.
# If unset, every operator endpoint answers 503.
# ORACLE_OPERATOR_KEYS=[{"keyId":"alice-1","actor":"alice","role":"oracle:admin","secretSha256":"<64 hex chars>"}]
# ORACLE_OPERATOR_READ_LIMIT_PER_MIN=120
# ORACLE_OPERATOR_WRITE_LIMIT_PER_MIN=20
# Failed authentications per IP per 15 minutes before lockout.
# ORACLE_OPERATOR_AUTH_FAILURE_LIMIT=10
# Set to false to skip bootstrapping config-file sources at startup.
# ORACLE_BOOTSTRAP_ON_STARTUP=true
# ── Oracle provider sandbox ──────────────────────────────────────────────────
# Mounts /sandbox/oracle/*: data providers submit signed readings and inspect
# the corroboration decisions and rejections. Readings stay in memory and are
# never published on-chain. Boot fails if a relayer key is configured or
# STELLAR_NETWORK is mainnet. See docs/oracle-provider-sandbox.md.
# ORACLE_SANDBOX_MODE=false
# ORACLE_SANDBOX_MIN_PROVIDERS=3
# ORACLE_SANDBOX_MAX_DEVIATION_RATIO=0.02
# ORACLE_SANDBOX_WINDOW_MS=300000
# ORACLE_SANDBOX_MAX_FUTURE_SKEW_MS=30000
# ORACLE_SANDBOX_MAX_PROVIDERS=500
# ORACLE_SANDBOX_MAX_SUBMISSIONS_PER_MINUTE=60
# ORACLE_SANDBOX_RETENTION_MS=86400000 # decisions/rejections purge after this
# ── Oracle pipeline shutdown & leadership ────────────────────────────────────
# On SIGTERM/SIGINT the oracle pipeline stops starting polls, waits up to this
# long for in-flight publishes and evidence writes, releases the leader lock,
# then Postgres/Redis close. Kept at least 5s below SHUTDOWN_GRACE_PERIOD_MS.
# If the drain deadline is hit, the abandoned tasks are logged by id and the
# process exits 1. A second SIGTERM/SIGINT forces an immediate exit. Keep
# docker-compose's stop_grace_period above SHUTDOWN_GRACE_PERIOD_MS.
# SHUTDOWN_GRACE_PERIOD_MS=30000
# ORACLE_SHUTDOWN_DRAIN_MS=20000
# Multi-replica: set to "redis" so only one replica polls and publishes.
# ORACLE_LEADER_LOCK=none
# ORACLE_LEADER_LOCK_KEY=refract:oracle:leader
# ORACLE_LEADER_LOCK_TTL_MS=30000
# ── Oracle data-source endpoints (off-chain) ─────────────────────────────────
# Ordered endpoint lists per provider adapter: primary first, then mirrors or
# other regions, as `url` or `label=url`, comma-separated. Calls go to the
# healthiest endpoint (primary preferred) and fail over within the same
# request. Without a list, COINGECKO_BASE_URL / DEFILLAMA_BASE_URL /
# STELLAR_HORIZON_URL (or the defaults) is the only endpoint.
# This is separate from SOROBAN_RPC_URLS, which covers on-chain calls.
# Every endpoint in a list belongs to the same provider: mirrors never count
# as independent sources in corroboration.
# Health: GET /api/v1/oracle/endpoints/health
# ORACLE_ENDPOINTS_COINGECKO=primary=https://api.coingecko.com/api/v3,mirror=https://cg-mirror.example/api/v3
# ORACLE_ENDPOINTS_DEFILLAMA=https://api.llama.fi
# ORACLE_ENDPOINTS_HORIZON=primary=https://horizon-testnet.stellar.org,backup=https://horizon-testnet.example
# ORACLE_ENDPOINT_FAILURE_THRESHOLD=3 # consecutive failures before taking an endpoint out
# ORACLE_ENDPOINT_COOLDOWN_MS=30000 # first cooldown; doubles per trip
# ORACLE_ENDPOINT_MAX_COOLDOWN_MS=300000
# ORACLE_ENDPOINT_SLOW_MS=2000 # latency EWMA that counts as degraded
# ORACLE_ENDPOINT_ATTEMPT_TIMEOUT_MS=5000 # defaults to ORACLE_HTTP_TIMEOUT_MS
# ── Oracle provider HTTP client (issue #188) ─────────────────────────────────
# Timeout/retry/rate-limit budgets are defined in src/oracle/httpClient.ts and
# documented in docs/provider-http-client.md. Responses are cached in Redis
# (REDIS_URL / REDIS_HOST); if Redis is down, calls go directly upstream.
# Horizon ledger finality: confirmations before a Horizon-derived reading counts toward quorum
# HORIZON_FINALITY_DEPTH=5
# final_only (default) | allow_provisional
# HORIZON_FINALITY_POLICY=final_only