Environment reference

Every environment variable the Ecosystem addon reads — vault, ScyllaDB, transaction providers, deposit monitoring and each chain family — with what breaks without it and which keys are dead.

4 min readUpdated 8 August 2026environment, configuration, reference, rpc

Only the explorer and transaction-provider block below is declared in .env.example. Every other variable on this page is added by hand to the project root .env, and every one requires a backend restart to take effect — provider instances and the ScyllaDB client are constructed at module load.

Admin → Ecosystem → Blockchains → Requirements shows which of these keys are currently set on your install, and its per-chain Tx history & explorer providers row is built from the runtime dispatcher itself, so it cannot disagree with what a real request does.

Vault

AES-256-GCM-encrypted master key, four colon-separated hex parts. Required for master-wallet creation, deposit-address generation and withdrawal signing. Generated once by scripts/kms/generate.mjs.
Passphrase that auto-unlocks the vault at boot. Without it the vault starts locked after every restart and stays locked until an admin unlocks it. At least 12 characters.

ScyllaDB

Ecosystem spot and futures order placement, order book and candles live here. Wallets, deposits and withdrawals do not depend on it.

Comma-separated ScyllaDB contact points.
Local datacenter name. Must match the name the Scylla node itself reports.
Scylla username. Authentication is attached only when both username and password are set.
Scylla password, paired with SCYLLA_USERNAME.
Keyspace for ecosystem orders, candles, order book, trades, the open-orders index and stop orders. Created automatically on first connection.
Keyspace for futures. Created automatically on first connection.
Connections per host in the local datacenter pool. Accepts 1-32; anything else uses the default.
Set to "false" to skip ScyllaDB entirely. Trading endpoints answer 503 and the connection retries stop.

Redis

Load-bearing for this addon specifically: pending deposits, balance caches, address locks and the engine lease all live in Redis. The core already requires it, so these are listed for completeness.

Redis host for the singleton client.
Redis port.
Redis AUTH password, only when the server requires it.
Redis logical database index.

The built-in EVM chain pattern

For each of ETH, BSC, POLYGON, FTM, OPTIMISM, ARBITRUM, BASE, CELO, RSK, HECO, CRONOS and MO:

Key Required Purpose
<CHAIN>_NETWORK Yes Selects the network. Decides which <CHAIN>_<NETWORK>_* keys are read. Must be one of the chain's own valid names.
<CHAIN>_<NETWORK>_RPC Yes HTTP(S) JSON-RPC endpoint. Provider initialisation throws without it.
<CHAIN>_<NETWORK>_RPC_WSS No WebSocket JSON-RPC. Without it, token-deposit monitors fall back to HTTP polling.
<CHAIN>_EXPLORER_API_KEY No Per-chain Etherscan key, tried before ETHERSCAN_API_KEY rather than instead of it. <CHAIN>_ETHERSCAN_API_KEY is read after it and before the global key.

The network segment is the network name uppercasedPOLYGON_NETWORK=matic means the runtime reads POLYGON_MATIC_RPC.

Valid network names per chain, and the chain IDs the RPC must serve, are listed in Supported blockchains.

The misspelled key — one T — is still read as a fallback by the admin balance endpoint and the legacy health check, but never by the real provider path. Set it alone and health reports Arbitrum as Up while deposits and withdrawals are broken. Always set ARBITRUM_MAINNET_RPC.

Explorer and transaction-history providers

Seven providers serve EVM transaction history, native-deposit detection, token transfer lists and contract ABI lookups. They are tried in a per-chain order with automatic failover, and every configured key for a provider is rotated through before the next provider is reached.

Two of them — Blockscout and Routescan — need no credential at all, so a chain with a hosted instance keeps working with nothing set here. Across the built-in chains that covers ETH, ARBITRUM, OPTIMISM, BASE, CELO and RSK on both of their networks plus POLYGON mainnet, and MO through its own explorer host. It does not cover Polygon Amoy, BSC, FTM, CRONOS or HECO — the per-chain picture is in Explorer coverage is not uniform.

Where the order comes from

Most specific wins, and exactly one of the three applies:

Source When it is used
TRANSACTION_PROVIDERS_<CHAIN> Set for that chain
TRANSACTION_PROVIDERS Set, and no per-chain variable exists for this chain
Built-in per-chain default Neither is set — the normal case

Whichever wins, every keyless provider that can serve the chain and is not already listed is appended to the end, so even a short pinned order keeps a last resort. TRANSACTION_PROVIDERS_STRICT=true turns that off.

Unknown names are dropped with a warning naming the seven that exist: etherscan, blockscout, routescan, ankr, moralis, covalent, nodereal.

Keys are lists, and can be scoped to one chain

Every key variable below may hold several comma-separated keys. The dispatcher walks the list on auth, plan and rate-limit failures before giving up on the provider, so one revoked or exhausted key does not take a chain's explorer down.

Each provider also reads a chain-scoped name, and the chain-scoped list and the global list are concatenated rather than "first wins" — a per-chain paid key is preferred and the shared free key still stands behind it as a spare.

Provider Chain-scoped names, in order Global
Etherscan <CHAIN>_EXPLORER_API_KEY, <CHAIN>_ETHERSCAN_API_KEY ETHERSCAN_API_KEY
Ankr <CHAIN>_ANKR_API_KEY ANKR_API_KEY
Moralis <CHAIN>_MORALIS_API_KEY MORALIS_API_KEY
Covalent <CHAIN>_COVALENT_API_KEY COVALENT_API_KEY
NodeReal <CHAIN>_NODEREAL_API_KEY NODEREAL_API_KEY
Blockscout <CHAIN>_BLOCKSCOUT_API_KEY BLOCKSCOUT_API_KEY
Routescan <CHAIN>_ROUTESCAN_API_KEY ROUTESCAN_API_KEY

A value that looks like a URL is ignored with a warning, and placeholder strings — changeme, or anything starting your_ or < — count as not set. That is what makes an unfilled .env.example line harmless, and it is also why a pasted <your-key-here> reads as no key on the diagnostics page rather than as a broken one.

A failed provider is remembered

Failures are classified, and the failing provider is then skipped for a cooldown on that chain and network: 10 minutes for a rejected key, 30 minutes for a plan rejection, 60 seconds for a rate limit, an hour for "does not index this chain", 30 seconds for a network error. The transient classes — rate limit, network error and an unclassified failure — double their cooldown on each consecutive failure, capped at 5 minutes; the deterministic ones do not grow. A key that comes back invalid or rate limited is quarantined across every chain at once, because that is a property of the key rather than of the chain it was used on.

That state is in memory, per process, and nothing in the admin panel clears it — the only reliable reset is restarting backend and cron, which hold separate breakers. If every provider for a chain is cooling down the dispatcher ignores the breaker and tries them anyway, so it can never be the sole cause of an outage.

The variables

Unified Etherscan API V2 multichain key. Used for transaction history and native-deposit detection on V2 chains, and by the admin token-holders and ABI-import paths — those now fail over to the keyless providers too, so it is no longer required on every chain. NOT inherited by custom EVM chains.
Ankr Advanced API key. Covers most mainnets and has a free tier — the broadest single keyed fallback. Does not index BSC testnet. The project must carry NO website/domain restriction: this is a server-side call and sends no Origin header, so a domain allowlist rejects every request with "Origin not allowed". Restrict by the server's outbound IP instead, under project → Settings → Security.
Moralis key. Covers ETH, BSC, POLYGON, ARBITRUM, OPTIMISM, BASE, FTM, CELO and their testnets. Not HECO, CRONOS, RSK, MO or custom chains. Caps a page at 100 records.
Covalent / GoldRush bearer token. One of the two providers that index BSC testnet (chain id 97) — Ankr and NodeReal do not.
NodeReal key. ETH and BSC mainnet only, and the BNB-Chain-endorsed replacement for the retired BscScan API. Caps a lookup at 100 records whatever the limit says — two calls of 50, incoming and outgoing, merged and deduplicated by hash.
Optional. Blockscout serves without a key; this only lifts the anonymous per-IP rate limit.
Optional. Routescan serves without a key; this only lifts the anonymous per-IP rate limit.
Comma-separated provider order for every chain, overriding the built-in per-chain defaults. Unknown names are dropped with a warning. Leave unset unless you have a reason — the built-in defaults already lead each chain with a provider that is free for it.
Comma-separated provider order for one chain, e.g. TRANSACTION_PROVIDERS_BSC. Beats TRANSACTION_PROVIDERS and the built-in default.
Set to "true" to stop the keyless Blockscout/Routescan tail being appended to whatever order you configured.
Per-attempt timeout in milliseconds. Values below 1000 are ignored. The diagnostics probe uses the same value, so the page cannot report a provider as failed that the runtime waits longer for and succeeds with.
Records requested per call. Accepts 1-10000. Etherscan's free tier dropped from 10,000 to 1,000 in July 2026; Moralis and Ankr cap a page at 100, and NodeReal at 100 — 50 per direction — regardless of this value.
Host of a self-hosted or long-tail Blockscout instance for one chain, e.g. BSC_BLOCKSCOUT_HOST. Beats the built-in chain-id map, and is the only way to give a keyless provider to the chains that have neither a hosted instance nor Routescan coverage — BSC, FTM, CRONOS, HECO and Polygon Amoy.

Deposit monitoring

Per-user WebSocket monitors always run. These keys control the optional background sweep and its tuning.

Set to the literal string "true" to enable the background deposit monitoring loop.
Set to "false" to disable the rate-limited background deposit scanner.
Interval for the background EVM deposit sweep, in milliseconds.
Interval for the background UTXO deposit sweep, in milliseconds.
Maximum explorer API calls per chain per process for the background sweep.
Parks UTXO withdrawals for this window in milliseconds to allow PSBT batching. Zero sends immediately.

UTXO chains

For BTC, LTC, DOGE and DASH:

Network. Only BTC accepts anything but mainnet: mainnet, testnet, testnet4, testnet3 or signet ("testnet" is an alias of testnet4). LTC, DOGE and DASH providers are mainnet-only.
Provider backend. mempool, blockcypher or node. Unrecognised values silently use the chain default; "node" is Bitcoin-only and falls back to BlockCypher elsewhere; DOGE and DASH always fall back to BlockCypher.
Comma-separated esplora API roots, tried in order, replacing the built-in public mirror list entirely. Only needed for a self-hosted mempool/esplora deployment.
Shared BlockCypher token for every UTXO chain. Effectively required for DOGE and DASH — anonymous limits cannot sustain deposit polling.
Withdrawal fee tier. Mempool provider: fastestFee, halfHourFee, hourFee, economyFee, minimumFee. BlockCypher path: regular or priority. Unknown values silently use the default.

Self-hosted Bitcoin Core, used only when BTC_NODE=node:

Bitcoin Core RPC host.
Bitcoin Core RPC port. Stays 8332 regardless of BTC_NETWORK — set it explicitly for testnet4 (48332), testnet3 (18332) or signet (38332).
Bitcoin Core rpcuser. Required when BTC_NODE=node.
Bitcoin Core rpcpassword. Required when BTC_NODE=node.
ZMQ rawtx publisher endpoint. Its presence is the on/off switch for the whole ZMQ realtime service, and it only initialises when BTC_NODE=node.
ZMQ rawblock publisher endpoint, for realtime confirmation updates.
ZMQ hashtx publisher endpoint, an optional monitoring stream.

Solana

Cluster selector. "mainnet" selects mainnet-beta and "testnet" selects testnet; ANY other value — including unset and the plausible-looking "mainnet-beta" — silently falls through to DEVNET. There is no custom RPC: the service always uses the public cluster endpoint.
A SEPARATE key from SOL_NETWORK, stamped into the wallet address record at generation time only. Keep it equal to SOL_NETWORK or the stored metadata lies about the cluster.
Read only by the admin token-deployment cost estimate. The runtime Solana service ignores it.

Tron

mainnet, shasta or nile. Any other value throws at service construction and kills every Tron flow — this is a total outage, not a degradation.
Full-host URL for mainnet. Defaults to the public TronGrid host. Shasta and Nile use TRON_SHASTA_RPC and TRON_NILE_RPC.
TronGrid API key, sent as TRON-PRO-API-KEY. Effectively required in production: anonymous quotas cause 429/403 and the deposit monitor self-terminates after ten consecutive errors.
Set to "true" for verbose TronWeb initialisation logging.

TON

"testnet" selects the testnet RPC and API-key pair; anything else, including unset, selects mainnet.
Toncenter-compatible JSON-RPC endpoint for mainnet. Testnet uses TON_TESTNET_RPC.
Toncenter API key for mainnet, sent as X-API-Key. Effectively required — anonymous toncenter is limited to roughly one request per second, which throttles deposit polling and the ten-attempt withdrawal confirmation loop. Testnet uses TON_TESTNET_RPC_API_KEY.

Monero

monerod JSON-RPC URL, including the /json_rpc path. The backend also points wallet-rpc at this daemon at startup.
monero-wallet-rpc JSON-RPC URL. If get_version fails at startup the whole XMR chain is disabled. Must be started with --wallet-dir.
Username for HTTP Digest auth against both daemon and wallet-rpc, when they run with --rpc-login.
Password paired with XMR_RPC_USER.
Does not select any endpoint — the real network is whatever monerod runs. Drives withdrawal address-prefix validation: mainnet 4/8, stagenet 5/7, testnet 9/A/B.

Keys that are read by nothing

These appear in shipped .env files and get copied between installs. Removing them changes no behaviour; keeping them wastes your time when you are debugging.

Key Reality
XMR_WALLET_USER Read by no code
XMR_WALLET_PASSWORD Read by no code. Wallet files are created and opened with an empty password regardless — if external tooling created them with a password, the backend cannot open them
BTC_ZMQ_HASHBLOCK Parsed into config, but its socket is never connected
LTC_NODE_HOST and siblings Dormant. Self-hosted node support is Bitcoin-only; the provider factory never routes to them
LTC_ZMQ_RAWTX and siblings ZMQ realtime is Bitcoin-only
CELO_ALFAJORES_RPC alfajores is not a supported CELO network
SOL_<NETWORK>_RPC, XMR_<NETWORK>_RPC Display only — the overview badge checks their presence; the chain services never read them

Custom EVM chains

Do not set these by hand. <CHAIN>_NETWORK, <CHAIN>_<NETWORK>_RPC, <CHAIN>_<NETWORK>_RPC_WSS and <CHAIN>_EXPLORER_API_KEY for an operator-defined chain are written into process.env from the ecosystem_custom_chain table at boot and rewritten on every registry reload. Edit the chain in Admin → Ecosystem → Custom EVM Chains instead.

Custom chains never inherit any global provider key — not ETHERSCAN_API_KEY, not ANKR_API_KEY, none of them. A first-party key on a third-party explorer turns a working keyless request into an "Invalid API Key" rejection, so only the chain's own <SYMBOL>_* keys and the keyless providers apply.