Environment variables Swap actually reads

The real names of every environment variable the Swap addon reads — per-chain RPC, aggregator credentials, screening and the wallet project id — and the nine names that are read by nothing.

6 min readUpdated 1 September 2026env, rpc, aggregators, credentials, walletconnect, testnet

Every variable on this page was read out of the code that consumes it. The list matters more than usual here, because a variable that is not read does not fail — it is simply absent, and the chain it was meant to configure reports "no endpoint" while the operator is looking at a .env line that says otherwise.

APP_ETHEREUM_RPC_URL, APP_BASE_RPC_URL, APP_ARBITRUM_RPC_URL, APP_OPTIMISM_RPC_URL, APP_POLYGON_RPC_URL, APP_BSC_RPC_URL, APP_ZEROEX_API_KEY, APP_ONEINCH_API_KEY and APP_ODOS_API_KEY appear nowhere in this codebase. Setting them configures nothing, and no console reports them as missing because nothing is looking for them.

These were printed in install.md step 3 up to release 6.1.x, so a .env copied from that page looks complete and leaves six chains unable to quote and two aggregators returning 401. That block now carries the real names; if you installed from an earlier copy, check your .env against the tables below.

Server RPC, one variable per chain

The real form is APP_DEX_RPC_<SLUG>, where <SLUG> is the chain's slug from the code registry (DEX_CHAIN_STATIC), uppercased. Fifteen chains, fifteen possible variables — you only need one per chain you enable.

Chain Chain id Variable Ecosystem fallback key
Ethereum 1 APP_DEX_RPC_ETHEREUM ETH
Optimism 10 APP_DEX_RPC_OPTIMISM OPTIMISM
Cronos 25 APP_DEX_RPC_CRONOS CRONOS
Rootstock 30 APP_DEX_RPC_ROOTSTOCK RSK
BNB Smart Chain 56 APP_DEX_RPC_BSC BSC
Polygon 137 APP_DEX_RPC_POLYGON POLYGON
Fantom Opera 250 APP_DEX_RPC_FANTOM FTM
TON 607 APP_DEX_RPC_TON TON
Base 8453 APP_DEX_RPC_BASE BASE
Arbitrum One 42161 APP_DEX_RPC_ARBITRUM ARBITRUM
Celo 42220 APP_DEX_RPC_CELO CELO
Avalanche 43114 APP_DEX_RPC_AVALANCHE none
Linea 59144 APP_DEX_RPC_LINEA none
TRON 728126428 APP_DEX_RPC_TRON TRON
Solana 1399811149 APP_DEX_RPC_SOLANA SOL

Avalanche and Linea have no ecosystem symbol, so for those two the variable (or a row override) is the only way to give the chain an endpoint. On every EVM chain and on Solana there is no public fallback — the Solana Labs public endpoint is deliberately not used, because it would make an unconfigured install appear to work in testing and fail under production load.

Three TON code paths hardcode toncenter's public endpoint rather than failing. The STON.fi adapter builds its TON client on APP_DEX_RPC_TON, else https://toncenter.com/api/v2/jsonRPC — that one reads the variable only, so a row override or the ecosystem fallback never reaches it. The TON branch of the confirmation sweep, and the TON chain readiness probe, both use the resolved rpcUrl, else https://toncenter.com.

A TON install with no APP_DEX_RPC_TON and no row override therefore quotes and settles against a public, unkeyed, hard rate-limited endpoint instead of reporting itself unconfigured — it looks healthy in testing and throttles under load, which is the exact failure the rule above exists to prevent. Set APP_DEX_RPC_TON, and APP_DEX_TON_API_KEY with it, on any TON install you intend to run.

How an endpoint is resolved, and who owns it

resolveServerRpc() tries three sources in a fixed order and records which one answered. That answer is the Server RPC column on the chain console, and it is reported instead of the URL.

  1. dexChain.rpcUrlOverride — the chain row, set in Admin → Swap → Chains. Source row.

  2. APP_DEX_RPC_<SLUG> — this addon's own variable. Source env.

  3. <KEY>_<NETWORK>_RPC — the ecosystem addon's endpoint, where <KEY> is the fallback key in the table above and <NETWORK> is the value of <KEY>_NETWORK, uppercased. Source eco.

When none of the three answers, the source is none and every server-side read of that chain fails with a 503 naming the variable to set — on TON alone, the paths above fall back to public toncenter instead of raising it.

Step 2 deliberately outranks step 3: a paid endpoint bought for swaps must not be silently replaced by one set years earlier for deposit scanning and rate-limited accordingly.

<KEY>_NETWORK is the ecosystem's setting. The .env shipped with this platform has FTM_NETWORK="testnet", CELO_NETWORK="alfajores" and TON_NETWORK="testnet" — so a chain that resolves through source eco on one of those is pricing against a testnet while the user's wallet is on mainnet, and ethers is pinned to a static network so it never notices.

There is a boot check for exactly this. When a chain resolves through eco and the ecosystem's active network is a different chain id, the platform logs CRITICAL and sets dexChain.status to false — the chain switches itself off, at every boot, until it has an endpoint of its own. If a chain will not stay enabled, this is why; set APP_DEX_RPC_<SLUG> or a row override and it stops — then switch the chain back on by hand, because the check only ever turns a chain off.

A chain with its own env or row endpoint is left alone even when the ecosystem is on a different network. That combination is a legitimate configuration and is logged at debug only. It is also the only way to run Swap on an install whose ecosystem is on a testnet: there is no testnet mode for swaps — every venue quotes mainnet liquidity and the registry carries mainnet ids only — so the ecosystem on Sepolia and Swap on Ethereum is the intended arrangement, not a mismatch to resolve. See Troubleshooting.

Aggregator credentials

The names below are the ones the adapters read. "Required" means the adapter cannot quote at all without it; "rate limit" means it works unkeyed and works better keyed.

Variable Adapter Without it
APP_DEX_ZEROEX_API_KEY 0x Required. Every quote fails before the request is made
APP_DEX_ONEINCH_API_KEY 1inch Required. Every endpoint is Bearer-authenticated and 401s
APP_DEX_JUPITER_API_KEY Jupiter (Solana) Optional — the lite endpoint is open; the key raises the rate limit
APP_DEX_LIFI_API_KEY LI.FI Optional — the public endpoint is open; the key raises the rate limit
APP_DEX_TRON_API_KEY SunSwap (TronGrid) Optional — TronGrid serves unauthenticated at roughly three requests a second
APP_DEX_TON_API_KEY STON.fi (toncenter) Optional — toncenter answers unkeyed and rate-limits hard, so settlement slows under load
KyberSwap No credential exists. x-client-id is a public attribution label, not a key
Odos No credential exists. Odos is paid through an on-chain referral code instead — see Providers

APP_DEX_TRON_API_KEY falls back to TRON_API_KEY and APP_DEX_TON_API_KEY falls back to TON_MAINNET_RPC_API_KEY, both of which the ecosystem may already have set.

Admin → Swap → Providers computes, per adapter, whether the variable it names is actually set — that is the credentialConfigured column, and it is a boolean rather than the value, because a readiness console that echoes a credential is a credential leak with a nice interface. An adapter that is switched on with an unset variable is green on every other screen and returns 502 on every quote.

Token screening

APP_DEX_GOPLUS_API_KEYtype: string
GoPlus API key. Optional — presence only raises the rate limit, and absence is the normal case.

Screening is off until dexScreeningEnabled is on and dexScreeningProvider names a screener; see Settings.

The wallet connection id

NEXT_PUBLIC_WALLET_CONNECT_PROJECT_IDtype: string
The Reown/WalletConnect project id. Inlined into the frontend bundle at BUILD time, and also read by the backend at runtime for wallet sign-in.

The variable is NEXT_PUBLIC_WALLET_CONNECT_PROJECT_IDWALLET_CONNECT, with the underscore. Install and set up prints NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, which is not the name anything reads.

NEXT_PUBLIC_* values are inlined at build time, so restarting the backend changes nothing — you must rebuild the frontend. The frontend throws at module load when it is unset, with that instruction in the message; the failure presents as the whole swap terminal refusing to render rather than as a quiet degradation.

It is also read by the backend at runtime, by the wallet sign-in and profile wallet-link routes. So it is a build-time value for one half of the platform and a runtime value for the other, and both halves must see the same id.

Which of these are secrets

Three rules, and they follow from where the common providers put their API key.

An RPC endpoint from either is a credential, not a setting. That is why:

  • APP_DEX_RPC_* and dexChain.rpcUrlOverride are never serialised to any client. GET /api/admin/dex/chain returns rpcConfigured and rpcSource instead of the URL, and the chain edit form can write the override but can never read it back.
  • dexChain.publicRpcUrl ships to every visitor's browser. It is a separate column precisely so that one field cannot be both. Never paste a keyed endpoint into it.
  • The Requirements console shows a sanitised host and a set/unset boolean, and nothing else — no path, no query string, no key.

Aggregator keys live in .env and never in the database. The dexProvider row holds the variable's name in apiKeyEnvVar; the value is read from the environment at call time and is never persisted.

What a working .env looks like

Only the chains you intend to enable, and only the aggregators you intend to use.

# Wallet connections. Inlined at BUILD time — rebuild the frontend after editing.
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=your-project-id

# Aggregators. The first two are required by their adapters; the rest raise a
# rate limit. KyberSwap and Odos take no credential at all.
APP_DEX_ZEROEX_API_KEY=
APP_DEX_ONEINCH_API_KEY=
APP_DEX_JUPITER_API_KEY=
APP_DEX_LIFI_API_KEY=

# Server-side RPC, one per chain you enable. These may carry a provider key in
# the path — they are never serialised to a browser.
APP_DEX_RPC_ETHEREUM=
APP_DEX_RPC_BASE=
APP_DEX_RPC_ARBITRUM=
APP_DEX_RPC_OPTIMISM=
APP_DEX_RPC_POLYGON=
APP_DEX_RPC_BSC=
APP_DEX_RPC_SOLANA=

# Optional: raise the rate limit on the non-EVM transports and the screener.
APP_DEX_TRON_API_KEY=
APP_DEX_TON_API_KEY=
APP_DEX_GOPLUS_API_KEY=

After editing, restart the backend and press Test on Requirements for each chain. The checklist reports what you configured; the probe reports what answers, and those two disagree more often than you would expect.