XChain Platform Indexer: Configuration Reference

Environment Variables

Configuration is loaded from a .env file and environment variables. Copy the .env.example file and configure before running.

Required Variables

Variable Description Example
DECODER_DB_HOST Decoder database hostname 127.0.0.1
DECODER_DB_PORT Decoder database port 3306
DECODER_DB_NAME Decoder database name XChain_BTC_Mainnet_Decoder
DECODER_DB_USER Decoder database username xchain
DECODER_DB_PASS Decoder database password secretpassword
INDEXER_DB_HOST Indexer database hostname 127.0.0.1
INDEXER_DB_PORT Indexer database port 3306
INDEXER_DB_NAME Indexer database name XChain_BTC_Mainnet_Indexer
INDEXER_DB_USER Indexer database username xchain
INDEXER_DB_PASS Indexer database password secretpassword
INDEXER_COIN Blockchain to index BTC, LTC, or DOGE
INDEXER_NETWORK Network to index mainnet, testnet, or regtest

Optional Variables

Variable Description Default
INDEXER_API_PORT API server listening port 3004
CORS_ORIGIN Allowed CORS origin for API requests http://localhost
INDEXER_RATE_LIMIT_RPM API requests per minute per IP 600
HUB_API_URL Hub JSON-RPC base URL used by the indexer’s hub client. Falls back to the URL passed in code when unset. (unset)
HUB_API_KEY API key sent with hub calls. Required whenever the hub runs keyed, which is always in validator mode. Treat as a credential. (unset)
HUB_REORG_API_KEY Separate key for the hub’s retraction rails (pushpricereorg, pushxcallreorg, pushdexreorg) when the hub gates them independently. Unset falls back to HUB_API_KEY, which is the legacy single-key behaviour. Treat as a credential. (falls back to HUB_API_KEY)
INDEXER_ALLOW_UNAUTHENTICATED Set to true to restore keyless pass-through on the gated methods (validator-reward writes, federation reads, gated exec). With no API key configured those methods otherwise fail closed. This is the explicit escape hatch for single-host and regtest nodes; do not set it on a node reachable beyond its own host. (unset, fails closed)
UTXO_TRACKER_URL UTXO-tracker hostname. Optional overall, but required for the DISPENSER fresh-address check. (unset)
UTXO_TRACKER_API_PORT UTXO-tracker port, paired with UTXO_TRACKER_URL. (unset)
BTC_INDEXER_DB_NAME Name of the BTC indexer database, read by recovery.js when rebuilding state from an anchored checkpoint on a non-BTC chain that needs to resolve BTC-anchored data. (unset)
DB_CONNECT_TIMEOUT MariaDB connection timeout in milliseconds 10000
DB_ACQUIRE_TIMEOUT Time to wait for a free pooled connection, in milliseconds 10000
DB_QUERY_TIMEOUT MariaDB query execution timeout in milliseconds 30000
MIGRATION_STRICT_CHECKSUM Set to 1 to make a schema-checksum mismatch fail closed at startup instead of logging and continuing. Off by default so a diverged schema does not cause a surprise fleet-wide boot failure; the operator path (node src/migrate.js) fails closed regardless. (unset, non-fatal)

Migration compatibility harness

bin/check-migration-old-code-compat.js is a standalone maintenance harness (run through bin/check-migration-old-code-compat.sh) that checks an older code ref against a migrated database schema. It reads its own environment, separate from the service configuration above:

Variable Description Default
OLD_REF Git ref of the older indexer code to check the migrated schema against (required)
DB_NAME Database the compatibility checks run against (required)
DB_HOST MariaDB host for that database (required)
DB_PORT MariaDB port (unset)
DB_USER MariaDB user (unset)
DB_PASS MariaDB password. Treat as a credential; pass it through the environment, never on a command line. (unset)
REPO Path to the indexer checkout whose git history holds OLD_REF (the repo containing the script)

Block-processing barriers and health

Variable Description Default
HUB_PRICE_SYNC_TIMEOUT_MS Price-sync barrier timeout. Before processing a block the indexer waits for its local price mirror to reach that block height, so native-coin fee validation is deterministic across operators. On timeout the block is deferred and retried rather than validated against a stale price copy. 60000
INDEXER_HEALTH_STALL_GRACE_MS How long with no committed block before the container reports unhealthy (503). Defaults to comfortably more than one barrier cycle so a single legitimate defer never flaps the healthcheck. Operational only, not a consensus parameter. max(2 × HUB_PRICE_SYNC_TIMEOUT_MS, 120000)
XCALL_DIRECT_PRESENCE_TIMEOUT_MS Call-presence barrier timeout in direct-hub-DB mode. With no HubDbSync mirror the cross-chain-call sync barrier is skipped, but reading the hub’s MariaDB directly does not guarantee an in-flight relay row has landed, so the indexer waits this long for it before the cross-chain-call pass. 10000
CHAIN_TIP_PUSH_MAX_LAG Skip pushing the chain tip to the hub while the indexer is more than this many blocks behind the decoder tip. During a bulk re-index, pushing a tip per historical block floods the hub’s rate limiter with 429s for no value: the hub only cares about the live tip. 100

Hub push queue and mirror

Variable Description Default
HUB_CONFIG_POLL_INTERVAL_MS Interval between hub config refresh polls 60000
HUB_DB_SYNC_POLL_INTERVAL Interval between hub-mirror table sync polls (used when HUB_DB_SYNC_ENABLED=true) 30000
HUB_SYNC_WATERMARK_INTERVAL_MS Interval at which the hub-mirror sync persists its progress watermark 10000
HUB_PUSH_RETRY_INTERVAL_MS How often the push-queue poller wakes to drain due rows 30000
HUB_PUSH_RETRY_BASE_MS Base backoff for a failed push. The wait grows as base × 2^(attempts-1), capped at HUB_PUSH_RETRY_MAX_MS. 30000
HUB_PUSH_RETRY_MAX_MS Backoff ceiling for push retries 600000 (10 min)
HUB_PUSH_MAX_ATTEMPTS Attempts before a push row is abandoned (about 30 minutes at the default backoff) 10
HUB_PUSH_FAILED_RETENTION_SECONDS How long an abandoned push row is kept before the sweep drops it. Without the sweep a long hub outage grows the queue table with no ceiling. Set to 0 to keep terminal rows forever. 604800 (7 days)
HUB_PUSH_PRUNE_INTERVAL_MS How often the queue prunes abandoned rows older than the retention window 3600000 (1 hour)

Fee quote and pre-flight

Variable Description Default
INDEXER_FEEQUOTE_MAX_PENDING Maximum concurrent in-flight feequote evaluations before new ones are rejected 8
INDEXER_FEEQUOTE_TIMEOUT_MS Per-request timeout for a feequote evaluation, measured from the moment the dry-run holds the transaction mutex 10000
INDEXER_FEEQUOTE_ACQUIRE_TIMEOUT_MS How long a feequote / preflight dry-run waits for the block-processing transaction mutex before answering busy: true, retryable: true. Keep it below the explorer’s INDEXER_API_TIMEOUT_MS, or the busy answer loses the race to a proxy timeout and the caller sees a 502 instead 2000
INDEXER_PREFLIGHT_MEMO_MAX Maximum memo length accepted by the read-only preflight endpoint 256

State-tree metrics

Variable Description Default
STATE_TREE_METRIC_INTERVAL_MS Interval for the state-tree orphan-statistics sweep. Set 0 to disable. 14400000 (4 h)
STATE_TREE_METRIC_MAX_NODES Node ceiling for a single metric pass, bounding the sweep’s cost on a large tree 2000000
INDEXER_SMT_TOUCH_AUDIT Set to 1 to audit, per block, the set of keys the state-commitment pass actually touched against the set the ledger rows say it should have, reporting the difference in both directions with the exact keys. That evidence cannot be recovered once the block is committed, which is why it is a flag rather than a post-hoc probe. Diagnostic only; off in normal operation. (unset, audit off)
INDEXER_TOUCH_GUARD Behaviour when the balances touched-set guard fails, meaning the ledger moved keys the commitment did not apply and balances_root would be committed incomplete (XC-897). The default throws and stops the block. Set to warn to log and commit anyway; the node then diverges from any node that full-rebuilds, so this is a deliberate, temporary escape hatch rather than an operational setting. (unset, fails closed)

Genesis

Genesis is pinned per network in the coin registry and the bundled dumps ship inside the Docker image, so a stock install needs none of these. They exist for regenerating or relocating the genesis inputs, and for drilling the genesis path on regtest. See XCHAIN Genesis.

Variable Description Default
GENESIS_LEDGER_PATH Path to the canonical genesis ledger CSV data/genesis/<coin>-ledger.csv
GENESIS_DUMP_PATH Path to the pre-derived genesis state dump. When present, the dump-import path runs and is verified against XCHAIN_GENESIS_DUMP_HASH (sha256 of the uncompressed content) plus a recheck of the genesis block hashes; absent, the canonical CSV derivation runs instead. data/genesis/<coin>-<network>-genesis-dump.ndjson.gz
GENESIS_BLOCK_TIMEOUT_MS Watchdog for the genesis block on the CSV-derivation path, which is the slow one 14400000 (4 h)
GENESIS_DUMP_TIMEOUT_MS Watchdog for the genesis block on the dump-import path. Kept tight (BTC measures around 15 s) so a wedged import is caught fast. 600000 (10 min)
GENESIS_AIRDROP_PATHS Comma-separated airdrop snapshot files to replay at genesis (none)
GENESIS_AIRDROP_HASHES Comma-separated sha256 digests pinning each GENESIS_AIRDROP_PATHS entry (none)
GENESIS_AIRDROP_AMOUNTS Comma-separated per-file airdrop amounts (none)
GENESIS_AIRDROP_SNAPSHOT_BLOCK Block height the airdrop snapshot was taken at (none)
CROSS_CHAIN_ROYALTY_REGTEST_TIME Regtest only. Override the cross-chain royalty activation time so the OFF/deny path stays drillable on a single-node stack. Deliberately regtest-scoped: two nodes with different values would disagree on consensus. 0 (activate at genesis)

Hub DB Price Source

Native-coin fee validation and FIAT settlement read the oracle tables (price_snapshots, oracle_prices) from the hub. There are two valid topologies:

  • Distributed (production default): the indexer runs on a different host from the hub. Set HUB_DB_HOST / HUB_DB_NAME (plus HUB_DB_PORT / HUB_DB_USER / HUB_DB_PASS) so price reads hit the hub’s data, optionally mirrored locally via HUB_DB_SYNC_ENABLED=true.
  • Single-host: the indexer’s own database already holds the synced hub copy, so no separate hub DB connection is needed and the oracle tables are read locally.

The two are indistinguishable from config alone: a node with no hub DB looks the same whether that is intentional (single-host) or an operator forgot HUB_DB_HOST / HUB_DB_NAME on a distributed node. In the latter case the indexer would silently value native-coin fees against stale or empty local price data, which on mainnet can diverge from the canonical fleet and fork the ledger.

To make the intent explicit, a mainnet indexer fails closed at startup when no hub DB is configured, unless INDEXER_ALLOW_LOCAL_PRICE_SOURCE=true is set to confirm an intentional single-host node. On testnet and regtest, single-host is the norm and there is no canonical fleet to diverge from, so the missing hub DB only logs a warning.

Variable Description Default
INDEXER_ALLOW_LOCAL_PRICE_SOURCE Acknowledge an intentional single-host setup (read oracle tables from the local indexer DB). Required to boot a mainnet node that has no HUB_DB_HOST / HUB_DB_NAME; ignored on testnet/regtest. unset (mainnet fails closed)

Coin-Specific Configuration

Each supported blockchain has a configuration file at src/configs/<COIN>.js (BTC.js, LTC.js, DOGE.js) that defines:

Parameter Description Example (BTC)
ISSUANCE_FEE_TOKEN XCHAIN fee for token issuance 1.00000000
ISSUANCE_FEE_SUBTOKEN XCHAIN fee for sub-token issuance 0.50000000
EXPIRATION_FEE_DEFAULT_DAYS Default listing duration 90 (3 months)
EXPIRATION_FEE_FREE_DAYS Free listing duration 182 (6 months)
EXPIRATION_FEE_PER_DAY XCHAIN fee per day beyond free period 0.00547945
ADDRESS.BURN Burn address (per network) 1Muhahahahhahahahahahhahahauxh9QX
ADDRESS.GAS Gas token issuer address (per network) 1BTNSGASK5En7rFurDJ79LQ8CVYo2ecLC8
ADDRESS.DONATE1 Protocol development donation address 1BTNSGASK5En7rFurDJ79LQ8CVYo2ecLC8
ADDRESS.DONATE2 Community development donation address 1BTNSGASK5En7rFurDJ79LQ8CVYo2ecLC8
ADDRESS.FEE_DESTINATION Native-coin fee collection address (per network), pinned in the bundled coin registry. Overridable at runtime via the XCHAIN_FEE_DESTINATION_<COIN>_<NETWORK> env var (e.g. XCHAIN_FEE_DESTINATION_BTC_REGTEST) on testnet/regtest only; a mainnet override is ignored with a warning, because fee acceptance is consensus and must not depend on operator environment. (coin registry)
ADDRESS.REWARD Validator reward pool: pre-funded, manually topped up, drained by COLLECT (BTC only; XCHAIN/COLLECT do not exist on LTC/DOGE, where the slot is unused) (set pre-launch)

Unified Gas Fee Schedule

After the activation block, fees for VM and staking actions are calculated using a gas-based schedule rather than the legacy flat fee constants. The following parameters are defined in each coin config file (src/configs/<COIN>.js) and are only applied to blocks at or after the activation height:

Parameter Description Example (BTC)
GAS_PRICE Base XCHAIN cost per unit of gas 0.00001
GAS_SCHEDULE Object mapping action types to their gas cost in gas units { DEPLOY: 100000, EXECUTE: 10000, STAKE: 5000, ... }
UNIFIED_EXPIRATION_FEE_FREE_DAYS Free listing duration under the unified schedule (replaces EXPIRATION_FEE_FREE_DAYS post-activation) 365
FEE_PAYMENT_MODE Reserved key indicating intended fee denomination per chain ('xchain' on BTC, 'native' on LTC/DOGE). Not currently read at runtime: see note below. 'xchain' (BTC)

Note on FEE_PAYMENT_MODE: This key is currently informational only and is not read by the fee-processing code. Fee payment mode is detected implicitly at runtime by detectFeePaymentMode() in src/utility.js, which derives the mode from the transaction itself: if a native-coin fee output to the configured fee destination is present it returns 'native'; if absent it returns 'xchain' on BTC (XCHAIN balance deduction is allowed as a fallback) and 'rejected' on LTC/DOGE (native coin is the only accepted fee on those chains). The FEE_PAYMENT_MODE config value is reserved for a future change that makes this detection explicit/config-driven; until then its value must mirror the implicit per-chain behavior to avoid surprising a later refactor.

The legacy flat fee constants (ISSUANCE_FEE_TOKEN, ISSUANCE_FEE_SUBTOKEN, EXPIRATION_FEE_PER_DAY, EXPIRATION_FEE_FREE_DAYS) remain in the coin config files and continue to apply for blocks before the activation height.

Indexer Constants

These values are defined in src/config.js and apply to all chains:

Token Rules

Parameter Value Description
GAS XCHAIN Gas token ticker name
NATIVE_TICK_DECIMALS 8 Decimal places for native coin amounts
MIN_TICK_LENGTH 1 Minimum ticker name length
MAX_TICK_LENGTH 250 Maximum ticker name length
TICK_CHARACTERS a-zA-Z0-9~!@#$%^&*()_+-={}[]:<>.? Allowed characters in ticker names
RESERVED_TICKS ['BTC','LTC','DOGE','XCHAIN'] Ticker names reserved by the protocol
MIN_TOKEN_DECIMALS 0 Minimum token decimal places
MAX_TOKEN_DECIMALS 18 Maximum token decimal places
MIN_TOKEN_SUPPLY 0.000000000000000001 Minimum token supply (10^-18)
MAX_TOKEN_SUPPLY 1000000000000000000000 Maximum token supply (10^21)

COINPay

Parameter Value Description
COIN_DECIMALS 8 Native coin decimal places (BTC/LTC/DOGE all use 8)
COINPAY_EXPIRATION 7200 COINPay obligation expiration in seconds (2 hours)

Field Limits

Parameter Value Description
MAX_TOKEN_DESCRIPTION 250 Maximum description length in characters
MAX_MEMO_LENGTH 250 Maximum memo length in characters
MAX_FILE_NAME_LENGTH 250 Maximum file name length
MAX_FILE_TYPE_LENGTH 255 Maximum MIME type length (per RFC 4288)
MAX_FILE_TITLE_LENGTH 250 Maximum file title length
MAX_BROADCAST_MESSAGE_LENGTH 250 Maximum broadcast message length
MAX_BROADCAST_VALUE_LENGTH 25 Maximum broadcast value length
MAX_MESSAGE_LENGTH 1048576 Maximum message content (1 MB)
MAX_MESSAGE_KEY_LENGTH 1048576 Maximum encryption key length (1 MB)
MAX_DISPENSES 1000 Maximum dispenses per dispenser

Timing and Delays

Parameter Value Description
BLOCK_CHECK_INTERVAL 5000 Milliseconds between block polling cycles
BLOCK_PROCESS_TIMEOUT 300000 Maximum milliseconds to process a single block
DISPENSER_LIST_DELAY 3600 Seconds before dispenser list updates take effect
DISPENSER_CLOSE_DELAY 3600 Seconds before dispenser close takes effect

Protocol Constants

Parameter Value Description
MESSAGE_ENCRYPTION_METHODS [1, 2, 3] 1 = ECIES, 2 = ECDH, 3 = AES
SLEEP_IMMEDIATE_METHODS [-1, 0] -1 = sleep indefinitely, 0 = resume immediately

Copyright © 2025–2026 Dankest, LLC

Based on XChain Platform by Dankest, LLC – https://dankest.llc

Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later) with a commercial license available for proprietary use.

You may use, modify, and distribute this material under the terms of the License. See LICENSE and NOTICE for full terms. See the licensing overview.

Edit this page on GitHub ↗