Aggregators and providers

All eight liquidity adapters this build ships — their chains, credentials, priorities and the three fee modes — plus the per-vendor prerequisites that make a configured fee collect nothing.

9 min readUpdated 6 August 2026providers, aggregators, fees, solana, tron, ton

Swap holds no liquidity. It asks every eligible adapter for a price in parallel, picks the best net result, and hands the user an unsigned transaction. Which adapters you enable decides your chain coverage, your fill quality, and — separately — whether you earn a fee at all.

The console is Admin → Swap → Routing → Providers, at /admin/dex/provider. Rows are seeded from a code registry, so you switch adapters on and off and order them; you cannot create or delete one.

Does the vendor's free tier permit commercial use? Several forbid it outright. You will not find out from the API — it keeps working — you find out when you are invoiced or cut off.

Is your integrator fee actually honoured? Two of the seven adapters need something arranged outside this console before a fee pays anything, and in every one of those cases the swap still succeeds and nothing reports an error. Check with a small swap and the fee ledger, per provider, before you count on the revenue.

The seven adapters

Provider Registry key Chains it prices Credential Fee mode Priority
0x 0x Ethereum, Optimism, BNB, Polygon, Base, Arbitrum One, Avalanche, Linea APP_DEX_ZEROEX_API_KEYrequired BUY 10
Jupiter jupiter Solana only APP_DEX_JUPITER_API_KEY — optional BUY 10
SunSwap sunswap TRON only APP_DEX_TRON_API_KEY — optional ONCHAIN_REFERRAL 10
STON.fi stonfi TON only APP_DEX_TON_API_KEY — optional ONCHAIN_REFERRAL 10
1inch 1inch Ethereum, Optimism, BNB, Polygon, Base, Arbitrum One, Avalanche, Linea APP_DEX_ONEINCH_API_KEYrequired SELL 20
KyberSwap kyberswap Ethereum, Optimism, BNB, Polygon, Base, Arbitrum One, Avalanche, Linea none SELL 20
LI.FI lifi Ethereum, Optimism, Cronos, Rootstock, BNB, Polygon, Base, Arbitrum One, Celo, Avalanche APP_DEX_LIFI_API_KEY — optional SELL 20

Every one of these is seeded on, deliberately: a row seeded off is a row an operator cannot switch on, and the failure it produces ("no aggregator is enabled") names nothing you could act on. This enables nothing by itself — the addon master switch and each chain's own switch are still off by default.

Keys live in .env, never in the database. The console reports whether a variable resolves; it never returns the value, and it never will. The exact names, their fallbacks, and the nine names that are read by nothing are in Environment variables.

The three fee modes, and why they are the most consequential difference

feeMode is shown on the Providers screen as Fee side and it is read-only, because it is a property of the vendor's settlement contract rather than something you configure.

Mode Adapters What it means
BUY 0x, Jupiter The fee arrives in the token the user receives. It is a Transfer in the receipt, so the accrual is proved from evidence rather than inferred.
SELL 1inch, KyberSwap, LI.FI The fee is deducted from what the user spends. When the sell leg is the chain's native coin there is no ERC-20 Transfer to find, so the accrual is arithmetic from the quote.
ONCHAIN_REFERRAL SunSwap, STON.fi There is no fee parameter on the request at all. Neither router has an integrator hook, so whatever the platform takes on TRON or TON is taken the way the direct-pool venue takes it, or not at all.

Two consequences that catch people out:

  • feeSide is written onto every quote and every swap row, and the accrual reader uses it to decide which token to look for. That is why KyberSwap's genuinely configurable fee side is pinned to the sell side in this build: a per-request choice would make the stored column mean "whatever was configured at the time", and a later settings change would silently reinterpret your history.
  • A SELL-side fee on a native-coin sell leg has no on-chain Transfer behind it, so those accruals are recorded as ESTIMATED rather than RECEIPT. See Fees and revenue for how the two read on a revenue report.

Your fee itself is one setting — dexFeeBps, Admin → Swap → Settings → Fees → Revenue, labelled Integrator Fee, default 0, maximum 300 (3%). The maximum is the vendors' ceiling, not an arbitrary one: 1inch rejects anything above 300 bps and LI.FI takes the same range.

What each adapter needs before it will pay you

Jupiter — the fee recipient is a token account, per mint

This is the single most likely misconfiguration in the whole addon, and it is not something an address can look wrong for.

On EVM, a fee recipient is an address and the aggregator sends the fee there. On Solana an SPL token can only be held by a token account owned by a wallet, one per mint, and each must be created and rent-funded before anything can arrive. So dexChain.feeRecipient on Solana stores the owner wallet, and the fee account is derived per mint.

Passing a wallet address where Jupiter wants a token account is rejected; passing an account that does not exist is worse, because on some route shapes Jupiter proceeds and simply collects nothing. This build refuses to guess: if the account cannot be proved to exist, the quote is served at zero fee and says so, rather than sending an unverified account. That includes the case where the RPC endpoint is merely unreachable — a transient outage must not turn into revenue that looks collected and is not.

Wallets is the screen that goes and looks, per mint, and tells you which accounts are missing.

LI.FI — a fee is only paid to an enabled integrator

LI.FI pays only integrators it has enabled fee collection for, arranged with LI.FI directly at portal.li.fi. Until that is done it does not quietly under-pay you — it rejects the entire quote, HTTP 400 code 1011, whenever the request carries a fee.

So while your platform fee is above zero and the integrator is unregistered, LI.FI prices nothing on any chain. Because it is the only adapter that reaches Cronos, Rootstock and Celo, those three chains stay listed in your console with no venue able to quote them, and every attempt fails as though no route existed. Register the integrator, or switch those chains off.

Full registration walkthrough: Getting your vendor accounts.

The integrator string is a constant in the build rather than a setting, precisely because it is the key LI.FI ties fee eligibility to: an operator editing it in a form would silently stop their own revenue while every swap kept working. It is sent on every request, fee or no fee, because it is public attribution rather than a credential.

LI.FI is also the only adapter here that prices Cronos, Rootstock and Celo — three chains with no other venue — and the only one that can be asked about a transaction after the fact.

STON.fi prices only — the message is built server-side

STON.fi's /v1/swap/simulate returns a price and no transaction: no payload, no BOC, no cell. Their expectation is that the browser assembles the message with their SDK.

Doing it that way would break the guarantee the rest of this addon rests on. Every quote stores a hash of its calldata and the receipt verifier checks that the transaction which settled is the one that was quoted — if the browser builds the message, the server never sees the bytes it is supposed to be binding. So the server builds the TON swap message, using STON.fi's own SDK: their encoder, our process, and bytes that exist server-side to be hashed and later verified.

Their router has no integrator-fee hook, so TON earns nothing through this venue.

SunSwap is a bare AMM, and earns nothing

SunSwap V2 on TRON is a UniswapV2 fork — verified, not assumed: its router reports its own name as UniswapV2Router02. Prices come from pool reserves read over TronGrid, so it needs no vendor account.

Being a bare V2 router it has no integrator-fee hook at all, exactly like the EVM direct-pool venue and for exactly the same reason: swapExactTokens… has no fee parameter and nowhere to put one. See Why a direct pool earns no swap fee — the argument is identical.

TRON also charges energy, not gas, and a user with staked TRX can pay nothing for a swap. Gas estimates on TRON are deliberately null rather than a converted number that would be wrong for most users.

Three kinds of credential, not two

Whether an adapter names an environment variable does not tell you whether it needs one, which is why the registry records the two facts separately and why the Providers screen's Credential column has three states rather than two.

Kind Adapters Behaviour with no key
Authentication 0x, 1inch The adapter throws AUTH before making a request — "0x is enabled but its API key is not configured". Every quote fails.
Rate limit only Jupiter, LI.FI, TRON (SunSwap), TON (STON.fi) Works, with two caveats. Jupiter's lite host and LI.FI's public endpoint are genuinely open — but LI.FI still refuses a fee'd quote until its integrator is registered, which is a separate thing from this key. TronGrid's unauthenticated ~3 req/s is below what one swap needs (a build spends four calls in under half a second), so the addon spaces its own unkeyed calls to compensate and each build costs about a second more. toncenter is rate-limited but sufficient.
None at all KyberSwap No credential exists. KyberSwap's x-client-id is a public attribution label, not a secret.

This distinction is load-bearing. Treating an unset variable as broken made the setup console warn about four "broken" adapters on a fresh install, three of which quoted perfectly — and a console whose out-of-the-box state is four false alarms is one an operator learns to scroll past, and then misses the real one.

The readiness report only counts a missing credential as a problem where the key is authentication, and its warning is precise: "Switched on with no credential in the environment: …. Every quote will fail authentication until the variable is set."

An unkeyed TON install is worth one extra thought: the confirmation poller calls toncenter once per pending swap, so under load a keyless install settles slowly rather than not at all.

Priority does not pick the winner

priority (lower first) does two things and neither is choosing a route:

  • It orders the race and breaks ties deterministically, so the same request always races the same order.
  • It is only ever compared within a chain. Jupiter at 10 never races 0x at 10, because 0x prices no Solana and Jupiter prices nothing else.

The winner is decided on net output: buy amount, minus the fee where the vendor takes it on the buy side, minus gas converted into buy-token units. The obvious rule — highest quoted output wins — is wrong in the direction that looks right on a dashboard, because a route quoting 0.3% more while costing four times the gas leaves the user with less, consistently.

When gas cannot be converted (either USD price missing on a thin token), the comparison drops to gross and records that it did, on the quote row, rather than pretending gas is free.

The four EVM aggregators added alongside 0x sit at priority 20 for one narrow reason: 0x's buy-side fee is the one this build has a fork test for, so on an exact tie it is the venue whose accrual is already proven. Every other venue wins whenever it actually prices better.

What the console lets you change

  1. Status — takes effect immediately, the adapter cache is invalidated on save
  2. Priority orders the race, it does not choose the venue
  3. Chain scope narrows; it can never widen
  4. Credential — three states, not two

The Providers screen edits four things: Status, Priority, Chain scope (supportedChainIds) and an endpoint override (baseUrl).

Chain scope is a narrowing, never a widening — the adapter's own capability list is the outer bound, and an empty scope means "no narrowing", which is why it renders as All chains it supports rather than as a blank cell. The PUT also refuses a chain id this build's registry does not carry, because that would be a provider switched on and silently unreachable.

Four fields are read-only on purpose:

Field Why editing it would break something
name It is the adapter registry key. Rename it and the row is orphaned from its code — the adapter stops being offered while the row still reads enabled.
apiKeyEnvVar It is the name of a variable, and a fact about how the adapter was written. Pointing it elsewhere does not move the credential; it makes the adapter read an empty one.
feeMode A property of the vendor's router. Writing it would let the fee resolver ask a SELL-only router for a BUY fee — which the vendor answers by ignoring the parameter, so you would quote a fee you never collect.
title / description Seeded from the registry so the console reads the same on every install, and so it does not drift from these pages.

Switching a provider off takes effect immediately: the adapter cache is invalidated by the save. Without that you would watch a disabled provider keep quoting for up to thirty seconds and reasonably conclude the toggle was broken.

Coverage gaps worth knowing before you enable a chain

  • Fantom Opera (250) has no aggregator in this build. LI.FI's own live chain list no longer contains it after Fantom's migration to Sonic, and no other adapter reaches it. A direct pool is the only route there.
  • Linea (59144) is absent from LI.FI, even though LI.FI supports it — this build has not identified the contract LI.FI routes Linea through, so claiming it would produce a venue failure at the last step instead of a clean "no route". 0x, 1inch and KyberSwap all price Linea.

Checking that it works

Admin → Swap → Requirements is the screen to use, not a per-provider test button — there is no credential-test action on the Providers screen in this build.

Its per-chain Test probe (POST /api/admin/dex/requirements/test, gated on edit.dex.chain because it makes outbound calls) talks to the chain, confirms the endpoint answers and answers as the chain id this build expects, and then asks candidates() — the same call the quote path makes — which adapters would be asked for a price. It reports them by name. Nothing is signed and no probe costs gas.

The global half of that report is what names a switched-on adapter with no credential, an addon master switch that is off, and quote-only mode. See Requirements.

Market data is a separate provider

Prices, candles and the trade feed do not come from an aggregator. They come from a market-data source selected by dexMarketProvider (default geckoterminal, alternatives codex and mock), with dexMarketProviderFallback for when the primary fails.

Set the fallback. A chart that stops drawing is the most visible failure on the page and has nothing at all to do with whether swaps still work.

Neither market-data key has a field on the Settings screen in this build, so they are set through the settings API rather than the form. They are listed in Settings with their defaults.