Adapter Architecture for Scanning 24 DEXs
Building a bot that scans multiple DEXs hits one problem immediately: each DEX swaps differently. UniswapV2-like constant product, V3's concentrated liquidity, Solidly's ve(3,3), DODO's PMM... Quote functions differ. Swap signatures differ. Everything is bespoke. Handle 24 DEXs and your bot's core quickly becomes unmaintainable if it has to understand each one.
The solution is classical: the adapter pattern.
Hide Differences Behind One Interface
Show the bot core only one interface. Two operations suffice: "Given X input, how much Y?" and "Actually swap for me."
interface IDEXAdapter {
// Quote: how much amountOut for amountIn
function getAmountOut(
address pool,
address tokenIn,
uint256 amountIn
) external view returns (uint256 amountOut);
// Execute: actual swap
function swap(
address pool,
address tokenIn,
uint256 amountIn,
address to
) external returns (uint256 amountOut);
}
Each protocol implements its own adapter:
UniV2Adapter—x * y = kconstant product; quotes are cheap on-chainV3Adapter— concentrated liquidity, tick math; must call Quoter contractSolidlyAdapter— stable/volatile pool distinction, ve(3,3) fee structureDODOAdapter— PMM model, oracle-based pricing curve; completely different math
The core doesn't care. It just calls adapter.getAmountOut(...).
Scale via Adapter Registration
Adding a DEX must not touch core logic. So register adapters at runtime. Assign each an ID; the router looks them up by ID.
mapping(uint8 => address) public adapters;
function registerAdapter(uint8 id, address adapter) external onlyOwner {
adapters[id] = adapter;
}
New DEX appears? Deploy one adapter, call registerAdapter(24, newAdapter). Core untouched. Off-chain, add the name to adapterNames and it joins the scan roster. This open-closed structure let us grow to 24 adapters while core logic stayed frozen.
Liquidity Check — Filter Traps
This is the most critical production lesson. A quote that looks like "10% arb" might be a mirage if the pool has no liquidity. Slippage eats the profit, or the swap fails outright.
Validate liquidity before putting a path in the execution candidate list. Quote well enough, but actual execution size matters more. Each protocol measures liquidity differently, so this check lives in the adapter layer too.
// Concept: is swap size reasonable vs pool reserves
func hasEnoughLiquidity(reserveIn, amountIn *big.Int) bool {
// Entry size vs pool reserves. If entry > threshold, slippage is excessive
// (V2 = reserve, V3 = active tick liquidity, Stable = curve segment)
threshold := new(big.Int).Div(reserveIn, big.NewInt(50)) // e.g., 2% of pool
return amountIn.Cmp(threshold) <= 0
}
V2 reserves, V3 active tick liquidity, stable curves—each judges differently. The key insight: a good quote and actual execution capacity are separate. This gate alone filters most wasted transactions.
Summary
- Hide DEX differences behind one adapter interface
- Protocol-specific adapters (UniV2 / V3 / Solidly / PMM) each implement that interface
- Adapter registration lets you add DEXs without touching core logic
- Liquidity check pre-filters traps that quote well but can't execute
The adapter pattern is textbook, but its power emerges when you face 24 heterogeneous on-chain protocols. Extensibility without core modification—that's what lets a bot survive long-term.
This is educational material, not investment advice. DeFi automation carries risk of total loss. You alone are responsible for outcomes.
댓글
댓글 쓰기