morphit/ops/env/indexer.env.example

791 lines
42 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ──────────────────────────────────────────────────────────────────
# Morphit indexer — environment variables
#
# Copy this file to /etc/morphit/indexer.env on the VPS, set the
# real values, chmod 0600, and chown the morphit user.
#
# See docs/RUN-A-MORPHIT-NODE.md step 8 for the deployment
# walkthrough; this file is the comprehensive reference.
#
# Every variable is parsed by apps/indexer/src/config/index.ts —
# if you set a value here that the schema rejects, the indexer
# refuses to boot with a clear error.
# ──────────────────────────────────────────────────────────────────
# ─── Database ──────────────────────────────────────────────────────
# Postgres connection string. Use a dedicated role with access
# limited to the morphit_indexer database. Rotate quarterly.
#
# The literal '__SET_BEFORE_DEPLOY__' is a sentinel: the indexer
# refuses to boot while the URL still contains it. Other rejected
# placeholders: 'CHANGEME', 'CHANGE_ME', 'CHANGE_ME_BEFORE_PRODUCTION',
# 'password', 'postgres'. The matching set is enforced by
# ops/postgres/init.sql at provisioning time too.
MORPHIT_INDEXER_DATABASE_URL=postgresql://morphit_indexer:__SET_BEFORE_DEPLOY__@localhost:5432/morphit_indexer
# Max number of pg pool connections. Default 10 fits a small
# instance. Raise to 25-50 for medium load; MUST stay below the
# Postgres server's max_connections setting (typically 100).
MORPHIT_INDEXER_DB_POOL_MAX=10
# ─── Chain identity ────────────────────────────────────────────────
# Blurt mainnet chain ID. Pinned at first run; the indexer
# refuses to boot if its recorded chain_id differs from this.
MORPHIT_INDEXER_CHAIN_ID=cd8d90f29ae273abec3eaa7731e25934c63eb654d55080caff2ebb7f5df6381f
# Comma-separated list of Blurt RPC endpoints. The indexer
# rotates between them on transport failure.
MORPHIT_INDEXER_RPC_ENDPOINTS=https://rpc.drakernoise.com,https://blurtrpc.dagobert.uk,https://rpc.blurt.blog,https://rpc.beblurt.com,https://rpc.blurt.one,https://blurt-rpc.saboin.com
# ─── Indexing behavior ────────────────────────────────────────────
# First block to index on a FRESH database. After that, the indexer
# resumes from the database's last_applied_block regardless of this
# value. Default is the Morphit genesis block (59441298) — the block
# in which the morphit / morphit-relay / morphit-fees accounts were
# created. No Morphit op exists before it, so starting earlier (e.g.
# block 0) replays years of unrelated Blurt history (multi-day waste,
# and your node would not be current at launch). Higher = faster
# first sync / less history; lower = more history / slower. To
# fast-forward an EXISTING node's cursor, use `morphit-ops fast-forward`.
MORPHIT_INDEXER_START_BLOCK=59441298
# How often to poll for new blocks. Blurt block time ≈ 3s; do
# not set lower than that.
MORPHIT_INDEXER_BLOCK_INTERVAL_MS=3000
# Catch-up backfill: how many block-fetch windows to prefetch CONCURRENTLY,
# each starting on a different RPC endpoint (spreads load across all nodes;
# a stalled node's window transparently falls back through the pool). Blocks
# are still applied strictly in order, one per transaction — only the network
# fetch is parallelised. 0 = auto = one window per configured endpoint.
MORPHIT_INDEXER_BACKFILL_CONCURRENCY=0
# Backoff after a transient chain error. Constant sleep, not
# exponential.
MORPHIT_INDEXER_ERROR_BACKOFF_MS=5000
# Lag threshold (blocks) above which /v1/health reports stale=true.
# 30 blocks ≈ 90 seconds.
MORPHIT_INDEXER_STALE_LAG_THRESHOLD=30
# ─── Head-block fast path (ADR-0051) ──────────────────────────────
# Delivers new activity to open pages within a few seconds instead of
# waiting ~45-60s for Blurt's last-irreversible block. A scanner tails
# the chain HEAD and streams provisional events over SSE. It NEVER
# writes the database — the normal poller (which only applies
# irreversible blocks) stays the sole source of truth. Clients dedupe a
# fast-path event against its durable copy, so nothing is shown twice.
# If a head block is later orphaned by a fork, something shown live
# simply never reaches durable history.
#
# There is NO on/off switch (v1.7.0, ADR-0051 — the old
# MORPHIT_INDEXER_CHAT_FASTPATH_ENABLED was removed, not renamed).
# Because the tailer never writes the DB, the worst a broken fast path
# can do is fail to make things fast; there is nothing to protect you
# from, and nobody prefers slow. If that variable is still in your env
# file it is now inert and can be deleted.
#
# How often (ms) the head scanner polls the chain head. 2000ms keeps
# worst-case end-to-end delivery under ~6s (≈3s block time + ≤2s poll
# wait + ~0.5s render). Lower = tighter latency + more head polls;
# raise it to reduce RPC load on a straining node.
MORPHIT_INDEXER_FASTPATH_INTERVAL_MS=2000
# ─── HTTP server ──────────────────────────────────────────────────
# Where the indexer listens. In production this is loopback only
# with nginx fronting; in dev you can bind 0.0.0.0 directly.
MORPHIT_INDEXER_LISTEN_HOST=127.0.0.1
MORPHIT_INDEXER_LISTEN_PORT=8081
# Public origin used for self-reference (logs, health responses).
# Set to your public URL. REQUIRED — there is no default.
MORPHIT_INDEXER_PUBLIC_ORIGIN=https://yourdomain.com
# Comma-separated list of exact-match origins allowed to make
# XHR calls. Your frontend's origin must be here.
MORPHIT_INDEXER_ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
# Per-IP rate limits (sliding-window per minute):
# - List endpoints (orderbook, feedback list, chat history)
# - Single-resource endpoints (profile, order by permlink)
MORPHIT_INDEXER_LIST_RATE_PER_MIN=120
MORPHIT_INDEXER_RESOURCE_RATE_PER_MIN=600
# Max request body size. The indexer is read-only; oversized
# bodies are rejected before routing.
MORPHIT_INDEXER_MAX_BODY_BYTES=4096
# ─── Trust anchors ────────────────────────────────────────────────
# The @morphit account's posting pubkey. Used to verify
# morphit_release_v1 ops. MUST match MORPHIT_OFFICIAL_POSTING_PUBKEY
# in the frontend — same trust anchor.
MORPHIT_INDEXER_OFFICIAL_POSTING_PUBKEY=BLT6CVC6C3PgmMe5xDtxFXJvGHaLnUTtcsK1ghHomDqLPWW7yeMp9
# The Blurt account name that goes with the pinned pubkey. Almost
# always `morphit`. Change ONLY if the account is ever migrated.
# This is a federation-wide trust anchor — all instances agree on
# the same value to verify Morphit's release ops.
MORPHIT_INDEXER_OFFICIAL_ACCOUNT_NAME=morphit
# THIS instance's operator account name. Used to gate per-instance
# moderation ops (operator-block lists, operator-payment-method
# additions). Defaults to OFFICIAL_ACCOUNT_NAME when unset, which
# is correct for the canonical morphit.io deployment. Community
# operators set this to their own Blurt account so they can
# curate their own instance without losing federation-wide release
# verification. Example for an operator running their own instance
# under the account @bob: MORPHIT_INDEXER_OPERATOR_ACCOUNT_NAME=bob
MORPHIT_INDEXER_OPERATOR_ACCOUNT_NAME=
# ─── Listing fees (BLURT-native, ADR-0011) ────────────────────────
# The Blurt account that receives listing-fee BLURT transfers.
# Orders with a matching fee transfer get fee_status='verified'.
# MUST match the frontend's FEE_RECIPIENT.
MORPHIT_INDEXER_FEE_RECIPIENT=morphit-fees
# Base listing fee in BLURT (NOT USD). Default 60 BLURT ≈ $0.12
# at typical recent prices. Sybil-tier multiplier escalates from
# the 4th order in 24h: 1×, 1×, 1×, 1.25×, 1.5625×, ... compounding.
MORPHIT_INDEXER_FEE_BASE_BLURT=60
# Tolerance band for fee amount verification. 0.001 = ±0.1%,
# absorbs rounding without admitting underpaid fees.
MORPHIT_INDEXER_FEE_TOLERANCE=0.001
# Account-creation fee fallback — used by /v1/chain-fee when
# condenser_api.get_chain_properties is unreachable. Set to the
# current witness-consensus value.
MORPHIT_INDEXER_ACCOUNT_CREATION_FEE_BLURT=100
# Attestation phase governs the attestor-eligibility gate (ADR-0014):
# launch — permissive (lower thresholds during ramp-up)
# steady — full thresholds applied
# Flip to 'steady' once the orderbook has settled and you want
# the full anti-sybil reputation gating.
MORPHIT_INDEXER_ATTESTATION_PHASE=launch
# ─── BLURT/USD price feed ─────────────────────────────────────────
# ON by default. Powers the USD equivalents the Morphit frontend
# shows next to BLURT amounts (the profile balance card + the
# listing-fee fiat echo). Source is CoinGecko with a static-floor
# fallback — a server-side call from your box, never user-facing. Set to false
# for a fully self-contained instance that makes zero external price
# calls (the UI then shows BLURT only).
MORPHIT_INDEXER_PRICE_FEED_ENABLED=true
# Static fallback price if the live feed fails.
MORPHIT_INDEXER_PRICE_FEED_STATIC_FLOOR=0.002
# Refresh cadence when the feed is on.
MORPHIT_INDEXER_PRICE_REFRESH_INTERVAL_MS=300000
# CoinGecko base URL (the external price source).
MORPHIT_INDEXER_COINGECKO_BASE_URL=https://api.coingecko.com/api/v3
# CoinGecko API key. Leave empty for the public free tier
# (rate-limited). Get a free key at coingecko.com/en/api
# if you need higher throughput.
# MORPHIT_INDEXER_COINGECKO_API_KEY=
# ─── cp372: additional no-key crypto→USD sources (averaged) ───────
# Coingecko is no longer the sole crypto→USD upstream. Coinpaprika
# and Kraken are fetched alongside it and the indexer takes a robust
# (median + outlier-rejected) average, so a single bad/biased source
# can't move the published price. Both are free + no-key.
MORPHIT_INDEXER_COINPAPRIKA_BASE_URL=https://api.coinpaprika.com/v1
MORPHIT_INDEXER_KRAKEN_BASE_URL=https://api.kraken.com/0/public
# ─── more crypto→USD sources for the median (USD denomination only) ──
# The more independent feeds we average, the more resilient pricing is
# to any one provider banning us, rate-limiting us, or going wrong.
# Each joins only when it returns a positive number; a wrong id / dead
# endpoint / missing listing / unset key returns null and is harmlessly
# excluded. All free + no-key unless noted.
# CryptoCompare — symbol-keyed aggregator (covers BLURT/BTC/XMR).
# Optional key raises the rate limit; basic price works without.
MORPHIT_INDEXER_CRYPTOCOMPARE_BASE_URL=https://min-api.cryptocompare.com
# MORPHIT_INDEXER_CRYPTOCOMPARE_API_KEY=
# CEX public tickers — BTC (XMR is delisted from most; Kraken above
# covers XMR). USDT pairs ≈ USD; the median absorbs the basis.
MORPHIT_INDEXER_BINANCE_BASE_URL=https://api.binance.com
MORPHIT_INDEXER_COINBASE_BASE_URL=https://api.exchange.coinbase.com
MORPHIT_INDEXER_OKX_BASE_URL=https://www.okx.com
MORPHIT_INDEXER_BYBIT_BASE_URL=https://api.bybit.com
# CoinLore — no-key, numeric-id aggregator (wired for BTC by default).
MORPHIT_INDEXER_COINLORE_BASE_URL=https://api.coinlore.net
# CoinCap (v3) — KEY-GATED; only joins the average when a key is set.
MORPHIT_INDEXER_COINCAP_BASE_URL=https://rest.coincap.io/v3
# MORPHIT_INDEXER_COINCAP_API_KEY=
# Messari — KEY-GATED; only joins the average when a key is set.
MORPHIT_INDEXER_MESSARI_BASE_URL=https://data.messari.io
# MORPHIT_INDEXER_MESSARI_API_KEY=
# api.blurt.blog — the Blurt chain's own BLURT/USD price_info endpoint
# (cp425). Joins the averaged BLURT/USD median as one more independent
# source; only accepted when it returns a value in a plausible band, so a
# wrong shape harmlessly returns null. Set empty to opt out.
MORPHIT_INDEXER_BLURT_PRICE_FEED_URL=https://api.blurt.blog/price_info
# Reject a source whose price differs from the cross-source median by
# more than this fraction (0.05 = 5%) before averaging. Defends the
# average against one wildly-off feed.
MORPHIT_INDEXER_PRICE_OUTLIER_TOLERANCE=0.05
# ─── cp372: USD→fiat FX feed (multi-currency $1-equivalent floor) ──
# Powers the per-currency first-order minimum + fiat fee echoes for
# non-USD instances. ON by default (matches the price feed). A
# generic server-side table fetch (base=USD) — privacy-preserving, no
# per-user query. Set =false on a USD-only instance that wants zero
# external FX calls.
MORPHIT_INDEXER_FX_FEED_ENABLED=true
# FX moves slowly; refresh hourly by default (ms).
MORPHIT_INDEXER_FX_REFRESH_INTERVAL_MS=3600000
MORPHIT_INDEXER_FX_FETCH_TIMEOUT_MS=5000
# Free, no-key, privacy-respecting FX providers (deep failover chain,
# averaged the same way as the crypto sources).
MORPHIT_INDEXER_FX_FRANKFURTER_BASE_URL=https://api.frankfurter.dev/v1
MORPHIT_INDEXER_FX_ER_API_BASE_URL=https://open.er-api.com/v6
MORPHIT_INDEXER_FX_CURRENCY_API_BASE_URL=https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1
# ─── cp127: morphit_native (self-sovereign) price feed ────────────
# Derive BLURT/USD from on-platform trade data instead of (or in
# addition to) the external source (Coingecko). See
# ADR-0039 for the design rationale. Opt-in: defaults to false on
# every instance. Enable once your platform has enough trade volume
# (≥3 distinct verified-fee traders posting BLURT-vs-USD or BLURT-
# vs-stablecoin orders within an 8-hour window) for the derivation
# to yield meaningful prices. When enabled, morphit_native slots
# BETWEEN coingecko and the static floor — external sources remain
# primary.
MORPHIT_INDEXER_PRICE_FEED_NATIVE_ENABLED=false
# When true, AND morphit_native is enabled, AND the cross-source
# disagreement monitor detects sustained material disagreement
# between morphit_native and external sources (default 25%
# divergence sustained 4+ hours), prefer morphit_native. Default
# false. Operators with mature on-platform data who trust their
# derived price more than external feeds can flip this on. WARNING:
# enabling this means an attacker who successfully manipulates
# morphit_native can override correct external prices. Only enable
# when you understand the tradeoff.
MORPHIT_INDEXER_PRICE_PREFER_NATIVE_WHEN_DISAGREEING=false
# Comma-separated lowercase stablecoin tickers the morphit_native
# fetcher considers for Tier 2 + the cross-stablecoin depeg
# detector. Default: usdt,usdc,dai. Remove any stablecoin your
# instance has disabled via MORPHIT_INDEXER_DISABLED_ASSETS.
MORPHIT_INDEXER_PRICE_FEED_STABLECOIN_KEYS=usdt,usdc,dai
# Per-asset plausibility envelope for the morphit_native fetcher.
# Default range matches BLURT historical trading band. You can
# TIGHTEN this for your asset, but the hardcoded outer bounds in
# morphitNativeFetcher.ts ([0.00001, 10000000]) take precedence —
# operator config cannot widen past those. See defense E in
# ADR-0039.
MORPHIT_INDEXER_PRICE_FEED_NATIVE_PLAUSIBLE_MIN=0.0001
MORPHIT_INDEXER_PRICE_FEED_NATIVE_PLAUSIBLE_MAX=0.1
# cp128: denomination fiat — the unit the indexer displays prices
# in (the fiat echo on /v1/listing-fee, the receipt endpoint, the
# drift/disagreement monitor state, the morphit_native fetcher's
# Tier 1 anchor query). Default 'USD' matches pre-cp128 behavior.
# Operators serving non-USD markets (or hedging against USD erosion /
# petrodollar collapse) can set this to any 3-8 character uppercase
# ticker:
#
# Major fiats: USD EUR GBP JPY CNY INR BRL RUB CAD AUD
# CHF MXN KRW IRR EGP ZAR AED IDR
# IMF basket: XDR (Special Drawing Rights)
# Precious metals: XAU (gold ounces), XAG (silver ounces)
# Crypto: BTC ETH XMR (rare — would put the indexer in a
# crypto-denominated display)
#
# IMPORTANT: when this is non-USD, the morphit_native fetcher's
# Tier 1 anchor looks for orders priced in your chosen fiat
# (asset=BLURT, fiat_currency=<your-denomination>). Tier 2
# (stablecoin) assumes the configured stablecoins are pegged to
# THIS denomination, not USD — an EUR-denominated instance should
# also configure MORPHIT_INDEXER_PRICE_FEED_STABLECOIN_KEYS with
# EUR-pegged stablecoins (which don't yet exist in Morphit's asset
# registry; today only USD-pegged stablecoins are wired). See
# ADR-0040 for the full design.
MORPHIT_INDEXER_PRICE_FEED_DENOMINATION_FIAT=USD
# cp129: Defense F — cross-instance peer disagreement detector.
# When ENABLED=true, the indexer periodically queries peer Morphit
# instances' /v1/price/morphit-native/receipt endpoint, stores
# observations to price_peer_observations, computes the peer median,
# and alerts on sustained median-vs-self disagreement.
#
# This is the cp127 "Defense F" from ADR-0039 (deferred → cp129),
# and catches the case where THIS indexer is the one being
# manipulated (rather than the manipulation happening at the
# trader level, which the cp127 sybil filters already address).
# See ADR-0041 for the full design.
#
# Defaults (built into the code):
# - 25% disagreement threshold
# - 4-hour sustained-disagreement window before alerting
# - 24-hour cooldown between alerts (prevents log spam)
# - 3 minimum peer observations required for comparison
# (below this, the monitor degrades silently to no-alert)
# - 7-day observation retention (table bounded automatically)
#
# REQUIRES:
# - MORPHIT_INDEXER_PRICE_FEED_ENABLED=true (need own derived
# price to compare against)
# - Federation peers reachable via /v1/instances (the federation
# prober's vetted set is what gets sampled)
#
# Leave OFF for brand-new instances with no federation peers yet;
# turn ON once you have ≥3 known good/quiet peers in the directory.
MORPHIT_INDEXER_PEER_PRICE_MONITOR_ENABLED=false
# How often (minutes) to sample peers. Default 30. Lower values
# = more responsive alerting but heavier peer query traffic; higher
# = less responsive but kinder to peer servers. Below 5 minutes
# starts to look like abuse to peers; above 120 risks missing
# short-duration manipulation windows.
MORPHIT_INDEXER_PEER_PRICE_SAMPLE_INTERVAL_MINUTES=30
# cp130: per-asset static-floor for the multi-asset price factory.
# Each asset has its own composite price source (Coingecko →
# morphit_native → static floor). These are the
# fallback prices each composite serves when all live upstreams
# have failed AND no value has cached successfully since boot.
#
# Per-asset defaults assume USD denomination. Operators in
# non-USD denominations should override these to match their
# unit — e.g. on a EUR-denominated instance, set
# MORPHIT_INDEXER_PRICE_FEED_BTC_STATIC_FLOOR to ~55_000 (BTC/EUR)
# instead of the USD-shaped 60_000.
#
# Static floors are intentionally rough — they never surface in
# normal operation; the composite source consults them only when
# every live upstream has failed. See ADR-0042.
MORPHIT_INDEXER_PRICE_FEED_BTC_STATIC_FLOOR=60000
MORPHIT_INDEXER_PRICE_FEED_XMR_STATIC_FLOOR=200
# ─── Featured-slot auction ────────────────────────────────────────
# Cost in BLURT to keep an order in the featured slot for one hour.
# Default 50 BLURT/hour.
MORPHIT_INDEXER_FEATURE_FEE_BLURT_PER_HOUR=50
# ─── BTC/XMR fee acceptance (ADR-0011 sub-phase 4b) ───────────────
# BOTH methods are OPT-IN. Default empty = disabled; orders
# claiming fee_method=btc (or xmr) will be rejected with a clear
# code until either (a) the operator fills in these values OR
# (b) a signed `morphit_release_v1` op carrying a `treasury`
# block lands on chain.
#
# **CHAIN-PIN PRECEDENCE (Part 106 + Part 107).** When a valid
# signed `morphit_release_v1` op carrying treasury fields is
# present in the indexer's release table, those chain-pinned
# values for ADDRESS and AMOUNT override the env-var values
# below. The env-vars act as the bootstrap fallback for fresh
# indexers that haven't seen a release op yet, and for community
# operators who want to stand up an instance before broadcasting
# their first signed release.
#
# **Part 108++: XMR per-payment proof verification.** XMR fees
# are now verified using user-submitted per-payment proofs
# (`tx_proof` field on the order op), not view-key-based
# decryption. No view key is required by ANY indexer. Every
# indexer can verify every payment independently using public
# explorer endpoints (or a local monerod RPC for maximum
# independence — see docs/OPERATIONS.md §40.4).
#
# **Part 109:** the `MORPHIT_INDEXER_XMR_FEE_VIEWKEY` env var has
# been removed entirely. If you have an old indexer.env with that
# variable, the line is harmless (zod ignores unknown env vars) —
# you can safely delete it next time you touch the file.
#
# **For the canonical morphit.io operator:** fill in BTC and
# XMR addresses + amounts before public launch. Then broadcast
# the signed release op with treasury block (BTC and XMR
# address+amount, no view key). Once the release op lands, the
# address+amount env values below are ignored (chain-pin wins).
# See docs/OPERATIONS.md §40.
#
# **For community operators:** Part 108++ removed the previous
# three-options dilemma for XMR. You now have ONE option: leave
# `MORPHIT_INDEXER_XMR_FEE_ADDRESS` empty and inherit canonical's
# chain-pinned address — your indexer verifies XMR fees
# independently using per-payment proofs supplied by users. No
# view key needed, no federation-trust path needed. Every
# instance is fully sovereign.
# Bitcoin fee. Default 416 sats targets ~$0.25/listing at $60K BTC.
# Recompute when prices drift via:
# tsx apps/indexer/scripts/recommend-fee-amounts.ts
#
# Address format: any valid Bitcoin address. Native segwit
# (`bc1q...`, 42 chars) is recommended for the lower fees.
# Legacy (`1...`) and P2SH (`3...`) also work. Do NOT use a
# testnet address (starts with `tb1`, `m`, or `n`) on mainnet.
MORPHIT_INDEXER_BTC_FEE_ADDRESS=
MORPHIT_INDEXER_BTC_FEE_SATOSHIS=416
MORPHIT_INDEXER_BTC_EXPLORER_URLS=https://blockstream.info/api,https://mempool.space/api
# Monero fee. 781250000 piconero (≈ 0.00078 XMR) targets
# ~$0.25/listing at $320 XMR.
#
# **MORPHIT_INDEXER_XMR_FEE_ADDRESS** — the public Monero
# address funds are sent to. Two valid formats:
# - Primary address: starts with `4`, exactly 95 characters.
# - Subaddress: starts with `8`, exactly 95 characters.
# Either works; the verifier auto-detects. Do NOT use a
# testnet address (starts with `9` or `B`).
#
# **MORPHIT_INDEXER_XMR_FEE_VIEWKEY** — REMOVED (Part 109).
# Previously this env var carried the operator's Monero
# private view key for XMR fee verification. Part 108++
# replaced view-key-based decryption with per-payment proof
# verification, making the view key unnecessary on the
# operator's box. Part 109 dropped the env var entirely:
# the indexer's config schema no longer recognizes it (zod
# ignores unknown env vars, so a stray line is harmless),
# no code path reads it, no docs reference it as needed.
#
# Why the design changed: pre-Part-108++ verification required
# the treasury wallet's view key on the operator's box, which
# meant only canonical morphit.io could verify XMR fees and
# forced community operators into "trust canonical / run own
# treasury / disable XMR" choices. Part 108++ replaced that
# with Monero's standard per-payment proof mechanism: users
# generate proofs from their own wallets, every indexer
# verifies them independently, no view key is required by
# anyone. Best privacy posture (zero shared secrets) and best
# decentralization posture (zero central dependency).
#
# **MORPHIT_INDEXER_XMR_EXPLORER_URLS** — comma-separated list
# of HTTPS Monero block-explorer URLs that support
# `txprove=1` proof verification on `/api/outputs`. Default
# ships with FIVE independent instances of the same reference
# codebase (`moneroexamples/onion-monero-blockchain-explorer`)
# — see the value below. Multi-explorer cross-check means a
# single compromised explorer can't lie about a verification.
# For maximum independence (priority #2 — decentralization),
# self-host a `monero-block-explorer` Docker container against
# your own `monerod` and add `https://localhost:<port>` to
# the list (or replace the list entirely). See
# docs/OPERATIONS.md §40.4 for explorer choice rationale.
MORPHIT_INDEXER_XMR_FEE_ADDRESS=
MORPHIT_INDEXER_XMR_FEE_PICONERO=781250000
# Default: five Monero explorers running the same
# `onion-monero-blockchain-explorer` reference codebase.
# All five expose the same `/api/outputs?txprove=1`
# endpoint used for per-payment proof verification.
# Multi-explorer cross-check means a single compromised
# explorer can't lie about a verification — all
# responding explorers must agree on amounts. See
# docs/OPERATIONS.md §40.4 for explorer choice rationale
# and self-hosted-monerod option (priority #2 maximum
# independence). Comma-separated, no spaces.
MORPHIT_INDEXER_XMR_EXPLORER_URLS=https://xmrchain.net,https://localmonero.co/blocks,https://monerohash.com/explorer,https://exploremonero.com,https://moneroexplorer.org
# ─── Part 109: quorum gates for fee verification ──────────────────
# Minimum number of explorers that must return a successful
# response before a fee verification promotes to `verified`.
# Default 1 preserves pre-Part-109 behavior (any single agreeing
# response is enough — the verifier still rejects DISAGREEMENT
# among responding explorers, but accepts a degraded-outage
# single-source result).
#
# Operators with 3+ explorers should bump these. With the default
# 5-explorer XMR list, 2 or 3 is a reasonable choice:
# - 2: agreement from at least 2 independent operators, but
# still tolerates 3 explorers being slow/down
# - 3: stronger cross-source guarantee, but verification falls
# back to `pending_external` more often during outages
#
# Server-side cross-check at boot: if the threshold exceeds the
# configured URL count, the indexer refuses to start.
MORPHIT_INDEXER_BTC_MIN_SUCCESSFUL_RESPONSES=1
MORPHIT_INDEXER_XMR_MIN_SUCCESSFUL_RESPONSES=1
# ─── Chat-link external explorer URLs (frontend) ─────────────────
# Templates for the "click a txid in chat" feature. When a
# counterparty pastes a txid in chat, the frontend substitutes
# the txid into the {txid} placeholder and opens the result in
# a new tab. Each template must:
# - start with https://
# - contain {txid} exactly where the txid should appear
#
# Defaults (used when these env vars are unset) — each explorer
# was chosen for being community-led, open-source-friendly, and
# free of third-party-analytics tracking (Morphit priority #1):
# BTC: https://mempool.space/tx/{txid} (Part 109)
# XMR: https://xmrchain.net/tx/{txid} (Part 109)
# BCH: https://blockchair.com/bitcoin-cash/transaction/{txid} (Part 122 cp21)
# LTC: https://litecoinspace.org/tx/{txid} (Part 122 cp24)
# DASH: https://insight.dash.org/insight/tx/{txid} (Part 122 cp27)
#
# USDT and USDC have no chat-link override env vars — the frontend
# dispatches to per-network explorers internally based on the
# address-share modal's network selection:
# USDT → Etherscan / Tronscan / Solscan / BscScan
# USDC → Etherscan / Solscan / Basescan / Polygonscan
# See ADR-0023 (USDT) and ADR-0028 (USDC).
#
# Privacy-conscious operators who self-host explorers can point
# these at their own instance to keep user IPs off third-party
# services. Override is per-operator (not per-user): every user
# on this Morphit instance opens links to whichever explorer the
# operator configured here.
#MORPHIT_FRONTEND_BTC_CHAT_LINK_URL=https://mempool.space/tx/{txid}
#MORPHIT_FRONTEND_XMR_CHAT_LINK_URL=https://xmrchain.net/tx/{txid}
#MORPHIT_FRONTEND_BCH_CHAT_LINK_URL=https://blockchair.com/bitcoin-cash/transaction/{txid}
#MORPHIT_FRONTEND_LTC_CHAT_LINK_URL=https://litecoinspace.org/tx/{txid}
#MORPHIT_FRONTEND_DASH_CHAT_LINK_URL=https://insight.dash.org/insight/tx/{txid}
#MORPHIT_FRONTEND_DOGE_CHAT_LINK_URL=https://blockchair.com/dogecoin/transaction/{txid}
#MORPHIT_FRONTEND_ZEC_CHAT_LINK_URL=https://mainnet.zcashexplorer.app/transactions/{txid}
#MORPHIT_FRONTEND_ARRR_CHAT_LINK_URL=https://explorer.piratechain.com/tx/{txid}
#MORPHIT_FRONTEND_DCR_CHAT_LINK_URL=https://dcrdata.decred.org/tx/{txid}
#MORPHIT_FRONTEND_SOL_CHAT_LINK_URL=https://explorer.solana.com/tx/{txid}
#MORPHIT_FRONTEND_ETH_CHAT_LINK_URL=https://eth.blockscout.com/tx/{txid}
#MORPHIT_FRONTEND_XRP_CHAT_LINK_URL=https://livenet.xrpl.org/transactions/{txid}
# ─── USDT multi-network chat-link explorer URL overrides ──────────
# Part 122 cp30 — these 4 env vars are the per-network USDT explorer
# overrides. They were declared in the indexer-client mirror at
# Part 121 cp3 but the indexer-side API never actually populated the
# response field; the frontend's defensive-fallback hid the breakage
# for 9 checkpoints. cp30-DD-11 closes the gap. Each undefined =
# frontend uses the bundled default for that network.
#MORPHIT_FRONTEND_USDT_ERC20_CHAT_LINK_URL=https://etherscan.io/tx/{txid}
#MORPHIT_FRONTEND_USDT_TRC20_CHAT_LINK_URL=https://tronscan.org/#/transaction/{txid}
#MORPHIT_FRONTEND_USDT_SPL_CHAT_LINK_URL=https://solscan.io/tx/{txid}
#MORPHIT_FRONTEND_USDT_BEP20_CHAT_LINK_URL=https://bscscan.com/tx/{txid}
# ─── USDC multi-network chat-link explorer URL overrides ──────────
# Part 122 cp30 — per-network USDC explorer overrides. 4 networks
# only; BEP-20 USDC intentionally not supported (ADR-0028 §1:
# Binance-Peg is a 2-custodian wrapper + 18-decimal precision
# divergence from Circle's 6-decimal standard on every native
# network). Each undefined = frontend uses the bundled default.
#MORPHIT_FRONTEND_USDC_ERC20_CHAT_LINK_URL=https://etherscan.io/tx/{txid}
#MORPHIT_FRONTEND_USDC_SPL_CHAT_LINK_URL=https://solscan.io/tx/{txid}
#MORPHIT_FRONTEND_USDC_BASE_CHAT_LINK_URL=https://basescan.org/tx/{txid}
#MORPHIT_FRONTEND_USDC_POLYGON_CHAT_LINK_URL=https://polygonscan.com/tx/{txid}
# ─── DAI multi-network chat-link explorer URL overrides ──────────
# Part 122 cp31 — per-network DAI explorer overrides. 4 EVM
# networks (Ethereum / Polygon / Base / Arbitrum One). SPL,
# TRC-20, and BEP-20 intentionally not supported (ADR-0029 §1:
# no canonical Maker-issued native DAI on those chains; existing
# wrapped variants defeat DAI's decentralization rationale).
# Each undefined = frontend uses the bundled default.
#MORPHIT_FRONTEND_DAI_ERC20_CHAT_LINK_URL=https://etherscan.io/tx/{txid}
#MORPHIT_FRONTEND_DAI_POLYGON_CHAT_LINK_URL=https://polygonscan.com/tx/{txid}
#MORPHIT_FRONTEND_DAI_BASE_CHAT_LINK_URL=https://basescan.org/tx/{txid}
#MORPHIT_FRONTEND_DAI_ARBITRUM_CHAT_LINK_URL=https://arbiscan.io/tx/{txid}
# ─── Trade-only asset operator stance ─────────────────────────────
# Morphit ships with all trade-only assets (USDT, USDC, DAI, BCH,
# LTC, DASH) enabled by default per Memory #25 (default-ON-instance-wide).
# Operators who object to specific assets for any reason — privacy
# trade-offs, ideological, regulatory comfort, etc. — can disable
# any subset INSTANCE-WIDE here. Users objecting to a specific
# asset simply pick a different instance (per-asset opt-out is
# OPERATOR-level, not user-level).
#
# Comma-separated tickers, case-insensitive. Whitespace tolerated.
# Hot-reload: change takes effect within 5 min (cached).
# The `morphit-ops init` wizard step 13 sets this interactively;
# this env-var path remains for post-deploy adjustment.
#
# Examples (uncomment one to apply):
#MORPHIT_INDEXER_DISABLED_ASSETS="" # accept all (default)
#MORPHIT_INDEXER_DISABLED_ASSETS="USDT" # BTC + XMR + BLURT + USDC + DAI + BCH + LTC + DASH + DOGE + ZEC + ARRR + DCR + SOL + ETH + XRP only
#MORPHIT_INDEXER_DISABLED_ASSETS="USDC" # BTC + XMR + BLURT + USDT + DAI + BCH + LTC + DASH + DOGE + ZEC + ARRR + DCR + SOL + ETH + XRP only
#MORPHIT_INDEXER_DISABLED_ASSETS="USDT,USDC,DAI" # privacy-pure: no centralized or partly-centralized stablecoins
#MORPHIT_INDEXER_DISABLED_ASSETS="USDT,BCH" # also refuse BCH
#MORPHIT_INDEXER_DISABLED_ASSETS="DASH" # refuse DASH only (e.g. masternode-concern operators)
#MORPHIT_INDEXER_DISABLED_ASSETS="DOGE" # refuse DOGE only
#MORPHIT_INDEXER_DISABLED_ASSETS="ZEC" # refuse ZEC only
#MORPHIT_INDEXER_DISABLED_ASSETS="ARRR" # refuse ARRR only (cp41)
#MORPHIT_INDEXER_DISABLED_ASSETS="DCR" # refuse DCR only (cp43)
#MORPHIT_INDEXER_DISABLED_ASSETS="SOL" # refuse SOL only (cp45)
#MORPHIT_INDEXER_DISABLED_ASSETS="ETH" # refuse ETH only (cp47)
#MORPHIT_INDEXER_DISABLED_ASSETS="XRP" # refuse XRP only (cp49)
#MORPHIT_INDEXER_DISABLED_ASSETS="USDT,USDC,DAI,BCH,LTC,DASH,DOGE,ZEC,ARRR,DCR,SOL,ETH,XRP" # only the 3 listing-fee-payable assets (BTC/XMR/BLURT)
# ─────────────────────────────────────────────────────────────────
# Disabled payment methods (cp208). Comma-separated canonical
# payment-method KEYS this instance will NOT offer. Default empty =
# offer every method in the canonical registry. Keys are lowercase
# (whitespace/case/trailing-comma tolerant). An order is rejected
# only when EVERY method on it is disabled; mixed orders are kept.
# Orders federated from OTHER instances using a disabled method still
# appear in your orderbook — the gate is only on new orders posted
# from here. The `morphit-ops init` wizard step 14 sets the common
# case (Barter) interactively; this env-var path covers the long tail
# and post-deploy adjustment. Canonical keys: see OPERATIONS.md
# §"Payment-method configuration" for the full list (in-person:
# barter_goods, cash_in_person, precious_metals; by_mail:
# cash_by_mail; online: paypal, zelle, venmo, wise, revolut, … ;
# crypto: pay_btc, pay_xmr, … — note online keys have NO pay_ prefix).
#
# Examples (uncomment one to apply):
#MORPHIT_INDEXER_DISABLED_PAYMENT_METHODS="" # offer all (default)
#MORPHIT_INDEXER_DISABLED_PAYMENT_METHODS="barter_goods" # money-only instance (no goods/services barter)
#MORPHIT_INDEXER_DISABLED_PAYMENT_METHODS="barter_goods,precious_metals" # also refuse precious-metals trades
#MORPHIT_INDEXER_DISABLED_PAYMENT_METHODS="paypal,venmo,zelle" # refuse specific US online rails
# ─── Low-balance auto-refill (ADR-0010 §3) ────────────────────────
# Refills active Morphit users who run low on BLURT, so they
# can keep using the chain. Distinct from operator-balance
# monitoring (next section) which alerts on YOUR own accounts.
# The relay's account name — excluded from the candidate list.
# Keep in sync with MORPHIT_RELAY_ACCOUNT in relay.env.
MORPHIT_INDEXER_RELAY_ACCOUNT=morphit-relay
# How often to scan. Default every 6 hours.
MORPHIT_INDEXER_LOW_BALANCE_REFILL_INTERVAL_MS=21600000
# Threshold below which a user qualifies.
MORPHIT_INDEXER_LOW_BALANCE_THRESHOLD_BLURT=0.5
# Active-user window — only refill users who have done a Morphit
# op in the last N days.
MORPHIT_INDEXER_LOW_BALANCE_ACTIVITY_WINDOW_DAYS=7
# Cooldown after a refill before the same user qualifies again.
MORPHIT_INDEXER_LOW_BALANCE_REFILL_COOLDOWN_DAYS=3
# BLURT sent per refill.
MORPHIT_INDEXER_LOW_BALANCE_REFILL_AMOUNT_BLURT=1
# Hard cap on accounts processed per scan.
MORPHIT_INDEXER_LOW_BALANCE_MAX_BATCH=50
# Operator alert room (Matrix). When set to a non-empty room ID
# (e.g. `!roomId:matrix.org`), alerts post to this room instead
# of the default DM channel from MORPHIT_INDEXER_OPERATOR_MATRIX_DM.
# Leave unset/empty (default) for DM-only routing.
# MORPHIT_INDEXER_OPERATOR_MATRIX_ROOM=
# ─── MCP server advertisement (cp167) ────────────────────────────
# Public-facing flag controlling whether /v1/instance reports
# this instance's MCP endpoint URL (so AI agent operators can
# discover it). The morphit-mcp systemd service runs (or not)
# independently of this flag — this is strictly about whether
# /v1/instance.mcp_url is non-null.
#
# Default: false. The wizard sets true when the operator opted
# in at step 20. Flip later by editing this line and restarting
# the indexer:
# sudo systemctl restart morphit-indexer.service
#
# See OPERATIONS.md §45 for the full MCP operator guide
# (setup, reverse proxy, resource cost, disable path).
MORPHIT_MCP_ADVERTISE=false
# ─── Operator-balance monitoring (alerts) ─────────────────────────
# Alerts the operator when YOUR OWN service accounts (relay,
# fees) drop below thresholds. Defaults are 0 = disabled, so
# an upgrade doesn't surprise you with new alerts. Set
# explicitly to opt in.
# How often to check. Default every 15 minutes.
MORPHIT_INDEXER_OPERATOR_BALANCE_INTERVAL_MS=900000
# Threshold for the relay account. Set to a value that gives
# you ~1 day of runway (relay burn ≈ 100 BLURT per signed-up
# user × your daily signup volume). Example: 5000 BLURT for
# an instance expecting 50 signups/day.
MORPHIT_INDEXER_OPERATOR_BALANCE_RELAY_THRESHOLD_BLURT=0
# Threshold for the fees account. Mostly informational — fees
# accumulate; an unexpected DECREASE could indicate a sweep.
MORPHIT_INDEXER_OPERATOR_BALANCE_FEES_THRESHOLD_BLURT=0
# Consecutive RPC failures before flipping the alerter into
# "balance probe failing" alarm state.
MORPHIT_INDEXER_OPERATOR_BALANCE_FAILURE_ALERT_THRESHOLD=3
# Optional: the relay's /v1/health URL so the indexer can probe
# signup velocity and recommend the kill switch when a low-balance
# alert correlates with a signup spike. Typical colocated
# deployment value: http://127.0.0.1:8080/v1/health?verbose=1
# Leave empty to disable the anomaly probe.
MORPHIT_INDEXER_RELAY_HEALTH_URL=
# ─── Operator visibility ──────────────────────────────────────────
# When true, /v1/health returns additional diagnostic fields
# (only when the request also passes ?verbose=1). Leave off in
# production unless actively debugging.
#
# PRIVACY NOTE: verbose mode exposes data that defaults to internal:
# - `last_error`: the raw error message from the indexer's most
# recent failure. This CAN include upstream hostnames, ports,
# internal IPs, database connection details, or RPC URLs — any
# of which could be in a Node ECONNREFUSED / ETIMEDOUT message
# or a Postgres / Blurt RPC error string. An attacker reading
# /v1/health?verbose=1 learns about your topology.
# - `explorers[].url`: the configured Monero explorer URLs (these
# are operator-chosen and not necessarily secret, but they may
# include self-hosted endpoints you'd rather not advertise).
# - `operator_balances`: per-account below/above-threshold state.
# Reveals which of your accounts are running low — useful for
# an attacker timing a drain attempt.
# Turn this on only when actively debugging, ideally behind your
# nginx access controls (IP-allowlist /v1/health?verbose=1 to your
# admin workstation), and turn it back off when done.
MORPHIT_INDEXER_VERBOSE_HEALTH=false
# ─── Per-instance branding (Phase D) ──────────────────────────────
# All optional. /v1/instance falls back to defaults when unset,
# so an unbranded instance still works. Most operators set these
# in morphit.config.env at the repo root rather than here.
# MORPHIT_INSTANCE_NAME=
# MORPHIT_INSTANCE_TAGLINE=
# MORPHIT_INSTANCE_CONTACT_URL=
# MORPHIT_INSTANCE_TOR_ADDRESS=
# MORPHIT_INSTANCE_LOKINET_ADDRESS=
# MORPHIT_INSTANCE_I2P_ADDRESS=
# I2P has two independent slots (set neither, one, or both): the always-
# resolvable b32 address and an optional human-readable vanity name. The
# legacy single var above is still honored (routed to the right slot by suffix).
# MORPHIT_INSTANCE_I2P_B32_ADDRESS=
# MORPHIT_INSTANCE_I2P_NAME_ADDRESS=
# MORPHIT_INSTANCE_NOSTR_PUBKEY=
# Optional ENS .eth name (DOMAIN.eth) — a registered Ethereum name pointing at
# this instance (typically via an ENS contenthash → IPFS copy of the site).
# Shown as a footer pill linking to an ENS gateway; not resolved server-side.
# MORPHIT_INSTANCE_ENS_NAME=
# MORPHIT_INSTANCE_ORIGIN=
# MORPHIT_INSTANCE_OPERATOR_TAG=
# MORPHIT_INSTANCE_SEO_TITLE=
# MORPHIT_INSTANCE_SEO_DESCRIPTION=
# MORPHIT_INSTANCE_SEO_KEYWORDS=
# cp119-A4: optional Twitter/X handle for twitter:site card
# attribution (e.g. "@morphit"). Must start with @; 1-15 alphanumeric
# +underscore chars. Leave unset if you don't have or don't want an
# X presence — the Twitter card still renders without it. See
# docs/OPERATIONS.md §43.
# MORPHIT_INSTANCE_SEO_TWITTER_SITE=
# ─── Dead env-var aliases (REMOVED — DO NOT SET) ──────────────────
# These were used by earlier versions and are now ignored. If
# present in your existing config, the indexer silently skips them.
# Listed here so operators upgrading from an older config don't
# wonder why their custom value isn't taking effect:
#
# MORPHIT_INDEXER_BLURT_PRICE_USD -> superseded by price source
# MORPHIT_INDEXER_FEE_BASE_USD -> superseded by FEE_BASE_BLURT
# MORPHIT_INDEXER_LISTING_FEE_AMORTIZATION_FACTOR -> superseded
# MORPHIT_INDEXER_LISTING_FEE_OPERATIONAL_MARGIN_BLURT -> superseded