Why a direct pool earns no swap fee
There is no integrator-fee hook on a raw AMM router — what that means for your revenue, why a wrapper contract was rejected, and the four checkable conditions under which this decision gets revisited.
A swap through a direct pool pays you nothing. Not a misconfiguration, not a setting somebody forgot — a property of the contract the swap goes through.
This page exists because that sentence is surprising, and because an operator who
does not know it will look for the missing fee in the settings, find a
dexFeeBps that is set, and conclude the product is broken.
The mechanism
feeRecipient and feeBps are an aggregator settlement-contract feature.
0x, 1inch and Odos run their own contracts between you and the liquidity, and
those contracts take a cut and forward it. That is where the integrator fee comes
from.
A Uniswap V2 router's swapExactTokensForTokens takes an amount, a minimum, a
path, a recipient and a deadline. There is nowhere to put a fee, because the
router is not a settlement layer — it is a thin wrapper over the pair contract's
swap(). The same is true of exactInputSingle on a V3 router.
So on a direct route the fee resolver short-circuits before it reads the
chain's feeRecipient at all, and reports VENUE_NO_FEE_HOOK. A chain with no
fee recipient configured is a perfectly valid configuration for a direct-only
pair, and refusing to quote one would be a bug.
What you do earn
The pool's own LP fee, on the liquidity you provided — 0.30% on a canonical V2 pair, 0.25% after the protocol switch, 0.17% on PancakeSwap V2, and whichever tier you chose on V3.
It is not platform revenue when it is earned. It becomes revenue when you withdraw and the withdrawal is recorded, and on a V2 pool it never can be — see Liquidity positions.
Why not a wrapper contract
The obvious fix is a small contract that takes the fee and forwards to the router. It was rejected, and the reasons are not stylistic:
It breaks the line the whole product is scoped around
This addon deploys zero contracts. That is the first sentence of its security statement, the thing an auditor checks first, and the reason it can claim to be non-custodial without qualification. A fee wrapper is a contract we wrote, we deployed, and users' funds pass through.
It moves funds through code we own
A wrapper sits between the user and the pool holding their tokens mid-transaction. Every bug in it is a bug that loses somebody else's money, and it would need an audit, a monitored upgrade path and an incident procedure — none of which this product has, because it has never needed them.
It is a new attack surface per chain
One deployment per chain, each with its own address to allowlist, verify and keep in a registry. A wrong address in that registry is a stranger's contract receiving the swap.
The revenue it would collect is small
A direct pool exists for pairs no aggregator will quote. Those are, by definition, the thinnest markets on the platform, and a percentage of very little is very little — measured against the cost above.
When this gets revisited
Uniswap's V3 SwapRouter02 carries sweepTokenWithFee, which can take a fee
in a multicall composition. It is not used, and the four conditions for using it
are written down here so that "we should look at this again" is a checkable
statement rather than a feeling:
- A fork test proves the multicall composition on the target deployment, with the output landing at the user and the fee at the recipient — not a testnet, not a mock, the deployment that will actually be used.
- A fork test proves the failure path leaves no tokens recoverable by a third party. A half-executed multicall that strands funds in the router is a worse outcome than earning nothing.
receipt.tsverifies a multicall shape. Today's verifier assertsreceipt.to === routerand a singleSwapevent from the quoted pool. A multicall invalidates both, and a receipt check that cannot describe the transaction it is checking is not a check.supportsFeeSweepis a per-deployment probed flag, never a global constant. Forks of the router do not all carry the function, and assuming they do turns a missing method into a reverted swap the user pays gas for.
Until all four hold, a direct route earns exactly zero by the nature of the router — which is a fact to state plainly, not a limitation to work around.
The settings that do apply
| Setting | What it does on a direct route |
|---|---|
dexFeeBps |
Nothing. There is nowhere to collect it |
dexDirectPoolMaxImpactBps |
Refuses a trade that would move the pool more than this |
dexDirectPoolMaxSlippageBps |
Caps the tolerance written into the calldata |
dexDirectPoolMinLiquidityUsd |
Refuses a pool too thin to trade against |
dexDirectPoolWinMarginBps |
How much better a pool must be to beat an aggregator |
The last one is the interesting one. A direct pool that ties with an aggregator should lose, because the aggregator route carries no conflict of interest — so the margin prices that conflict rather than pretending it is not there.