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.
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.
-
dexChain.rpcUrlOverride— the chain row, set in Admin → Swap → Chains. Sourcerow. -
APP_DEX_RPC_<SLUG>— this addon's own variable. Sourceenv. -
<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. Sourceeco.
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
Screening is off until dexScreeningEnabled is on and dexScreeningProvider
names a screener; see Settings.
The wallet connection id
The variable is NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID — WALLET_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_*anddexChain.rpcUrlOverrideare never serialised to any client.GET /api/admin/dex/chainreturnsrpcConfiguredandrpcSourceinstead of the URL, and the chain edit form can write the override but can never read it back.dexChain.publicRpcUrlships 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.