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.
ENCRYPTED_ENCRYPTION_KEYtype: secret
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.
ENCRYPTION_KEY_PASSPHRASEtype: secret
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.
Ecosystem spot and futures order placement, order book and candles live here.
Wallets, deposits and withdrawals do not depend on it.
SCYLLA_CONNECT_POINTStype: stringdefault: 127.0.0.1:9042
Comma-separated ScyllaDB contact points.
SCYLLA_DATACENTERtype: stringdefault: datacenter1
Local datacenter name. Must match the name the Scylla node itself reports.
SCYLLA_USERNAMEtype: string
Scylla username. Authentication is attached only when both username and password are set.
SCYLLA_PASSWORDtype: secret
Scylla password, paired with SCYLLA_USERNAME.
SCYLLA_KEYSPACEtype: stringdefault: trading
Keyspace for ecosystem orders, candles, order book, trades, the open-orders index and stop orders. Created automatically on first connection.
SCYLLA_FUTURES_KEYSPACEtype: stringdefault: futures
Keyspace for futures. Created automatically on first connection.
SCYLLA_LOCAL_CONNECTIONStype: numberdefault: 4
Connections per host in the local datacenter pool. Accepts 1-32; anything else uses the default.
SCYLLA_ENABLEDtype: booleandefault: true
Set to "false" to skip ScyllaDB entirely. Trading endpoints answer 503 and the connection retries stop.
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_HOSTtype: stringdefault: 127.0.0.1
Redis host for the singleton client.
REDIS_PORTtype: numberdefault: 6379
Redis port.
REDIS_PASSWORDtype: secret
Redis AUTH password, only when the server requires it.
REDIS_DBtype: numberdefault: 0
Redis logical database index.
For each of ETH, BSC, POLYGON, FTM, OPTIMISM, ARBITRUM, BASE,
CELO, RSK, HECO, CRONOS and MO:
The network segment is the network name uppercased — POLYGON_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.
Most specific wins, and exactly one of the three applies:
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.
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.
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.
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.
ETHERSCAN_API_KEYtype: secret
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_API_KEYtype: secret
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_API_KEYtype: secret
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_API_KEYtype: secret
Covalent / GoldRush bearer token. One of the two providers that index BSC testnet (chain id 97) — Ankr and NodeReal do not.
NODEREAL_API_KEYtype: secret
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.
BLOCKSCOUT_API_KEYtype: secret
Optional. Blockscout serves without a key; this only lifts the anonymous per-IP rate limit.
ROUTESCAN_API_KEYtype: secret
Optional. Routescan serves without a key; this only lifts the anonymous per-IP rate limit.
TRANSACTION_PROVIDERStype: string
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.
TRANSACTION_PROVIDERS_<CHAIN>type: string
Comma-separated provider order for one chain, e.g. TRANSACTION_PROVIDERS_BSC. Beats TRANSACTION_PROVIDERS and the built-in default.
TRANSACTION_PROVIDERS_STRICTtype: booleandefault: false
Set to "true" to stop the keyless Blockscout/Routescan tail being appended to whatever order you configured.
TRANSACTION_PROVIDER_TIMEOUT_MStype: numberdefault: 12000
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.
TRANSACTION_PROVIDER_LIMITtype: numberdefault: 1000
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.
<CHAIN>_BLOCKSCOUT_HOSTtype: string
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.
Per-user WebSocket monitors always run. These keys control the optional
background sweep and its tuning.
ENABLE_DEPOSIT_MONITORINGtype: booleandefault: false
Set to the literal string "true" to enable the background deposit monitoring loop.
ECOSYSTEM_BACKGROUND_SCANtype: booleandefault: true
Set to "false" to disable the rate-limited background deposit scanner.
EVM_CHECK_INTERVAL_MStype: numberdefault: 300000
Interval for the background EVM deposit sweep, in milliseconds.
UTXO_CHECK_INTERVAL_MStype: numberdefault: 900000
Interval for the background UTXO deposit sweep, in milliseconds.
API_THRESHOLDtype: numberdefault: 100
Maximum explorer API calls per chain per process for the background sweep.
UTXO_BATCH_WINDOW_MStype: numberdefault: 0
Parks UTXO withdrawals for this window in milliseconds to allow PSBT batching. Zero sends immediately.
For BTC, LTC, DOGE and DASH:
BTC_NETWORKtype: stringdefault: mainnet
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.
BTC_NODEtype: stringdefault: mempool
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.
BTC_MEMPOOL_API_URLtype: url
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.
BLOCKCYPHER_TOKENtype: secret
Shared BlockCypher token for every UTXO chain. Effectively required for DOGE and DASH — anonymous limits cannot sustain deposit polling.
BTC_FEE_RATE_PRIORITYtype: stringdefault: halfHourFee
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:
BTC_NODE_HOSTtype: stringdefault: 127.0.0.1
Bitcoin Core RPC host.
BTC_NODE_PORTtype: numberdefault: 8332
Bitcoin Core RPC port. Stays 8332 regardless of BTC_NETWORK — set it explicitly for testnet4 (48332), testnet3 (18332) or signet (38332). The requirements report warns when BTC_NETWORK names a test network and this is unset or still 8332, and a refused connection on the diagnostics row prints the port it tried.
BTC_NODE_USERtype: secret
Bitcoin Core rpcuser. Required when BTC_NODE=node.
BTC_NODE_PASSWORDtype: secret
Bitcoin Core rpcpassword. Required when BTC_NODE=node.
BTC_ZMQ_RAWTXtype: string
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.
BTC_ZMQ_RAWBLOCKtype: stringdefault: tcp://127.0.0.1:28332
ZMQ rawblock publisher endpoint, for realtime confirmation updates.
BTC_ZMQ_HASHTXtype: stringdefault: tcp://127.0.0.1:28334
ZMQ hashtx publisher endpoint, an optional monitoring stream.
SOL_NETWORKtype: string
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. The public cluster endpoint is used unless SOL_
_RPC names your own.
SOL_MAINNET_RPCtype: url
Comma-separated list of Solana RPC URLs for the cluster SOL_NETWORK names — SOL_MAINNET_RPC, SOL_TESTNET_RPC, SOL_DEVNET_RPC. When set it replaces the public cluster endpoint, which rate-limits under real deposit load. The pool prefers an untried endpoint, then the fastest, so every entry must be on the same cluster.
SOL_MAINNET_RPC_FALLBACKtype: url
An optional second list, appended after the first. Same format.
SOLANA_NETWORKtype: stringdefault: mainnet
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.
SOLANA_RPC_URLtype: urldefault: https://api.mainnet-beta.solana.com
Read only by the admin token-deployment cost estimate. The runtime Solana service ignores it — it dials SOL_
_RPC, or the public cluster when that is unset.
TRON_NETWORKtype: stringdefault: mainnet
mainnet, shasta or nile. Any other value throws at service construction and kills every Tron flow — this is a total outage, not a degradation.
TRON_MAINNET_RPCtype: urldefault: https://api.trongrid.io
Full-host URL for mainnet. Defaults to the public TronGrid host. Shasta and Nile use TRON_SHASTA_RPC and TRON_NILE_RPC.
TRON_API_KEYtype: secret
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.
DEBUG_TRONtype: boolean
Set to "true" for verbose TronWeb initialisation logging.
TON_NETWORKtype: stringdefault: mainnet
"testnet" selects the testnet RPC and API-key pair; anything else, including unset, selects mainnet.
TON_MAINNET_RPCtype: urldefault: https://toncenter.com/api/v2/jsonRPC
Toncenter-compatible JSON-RPC endpoint for mainnet. Testnet uses TON_TESTNET_RPC.
TON_MAINNET_RPC_API_KEYtype: secret
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.
XMR_DAEMON_RPC_URLtype: urldefault: http://127.0.0.1:18081/json_rpc
monerod JSON-RPC URL, including the /json_rpc path — write http://, the scheme is ignored and plain HTTP is dialled either way. Replaced by XMR_
_RPC for daemon queries when that is set, but still the address wallet-rpc is initially pointed at until a pool daemon answers.
XMR_MAINNET_RPCtype: url
Comma-separated list of monerod JSON-RPC URLs for the network XMR_NETWORK names — XMR_STAGENET_RPC, XMR_TESTNET_RPC. When set it replaces XMR_DAEMON_RPC_URL for every daemon query. The endpoints form a failover pool that prefers an untried one and then the fastest, so every entry must be a healthy daemon on the same chain; the diagnostics probe them all.
XMR_MAINNET_RPC_FALLBACKtype: url
An optional second list, appended after the first. Same format; set on its own it also replaces XMR_DAEMON_RPC_URL.
XMR_WALLET_RPC_URLtype: urldefault: http://127.0.0.1:18083/json_rpc
monero-wallet-rpc JSON-RPC URL. If get_version fails at startup the whole XMR chain is disabled. Must be started with --wallet-dir.
XMR_RPC_USERtype: secret
Username for HTTP Digest auth against both daemon and wallet-rpc, when they run with --rpc-login.
XMR_RPC_PASSWORDtype: secret
Password paired with XMR_RPC_USER.
XMR_NETWORKtype: stringdefault: mainnet
Names which daemon-list key is read (XMR_
_RPC) and drives withdrawal address-prefix validation — mainnet 4/8, stagenet 5/7, testnet 9/A/B. It does not move the daemon to another chain: the real network is whatever monerod runs, and the diagnostics compare the two.
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.
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.