DEX Aggregation in VoidDex: Building an Optimal Route Finder
Berke (pzzaworks)
January 5th, 2026
Finding the best swap rate isn't as simple as checking one DEX. VoidDex aggregates quotes from multiple protocols, evaluates split routes, considers multi-hop paths, and calculates the optimal execution strategy. This article covers the backend architecture that makes this work.
The NestJS backend organizes quote logic into specialized services:
TypeScript
@Module({ imports:[ConfigModule, ProtocolModule, PoolModule], controllers:[QuoteController], providers:[ QuoteService,// Main entry point DexQuoteService,// Fetches quotes from DEXes LiquidityGraphService,// Builds graph of available liquidity PathfinderService,// Finds optimal paths through the graph RouteQuoteService,// Gets quotes for discovered routes RouteOptimizerService,// Optimizes split routing FeeCalculatorService,// Calculates fees in WETH PriceService,// Token price lookups], exports:[QuoteService],})exportclassQuoteModule{}
The routing architecture uses a dual-flow approach: a primary pathfinder flow for discovering multi-hop routes, with a fallback to direct DEX quotes for simple pairs.
The RouteQuoteService is critical for getting accurate quotes on discovered routes. It simulates each hop in sequence:
TypeScript
@Injectable()exportclassRouteQuoteService{/**
* Get quotes for all discovered routes
*/asyncgetQuotesForRoutes( chainId:number, provider: PublicClient, routes: DiscoveredRoute[], amountIn: bigint, toDecimals:number,):Promise<RouteQuote[]>{const quotePromises = routes.map((route)=>this.getQuoteForRoute(chainId, provider, route, amountIn, toDecimals));const results =awaitPromise.all(quotePromises);// Filter out failed quotes and sort by output (best first)const validQuotes = results
.filter((q): q is RouteQuote => q !==null).sort((a, b)=>(b.amountOut > a.amountOut ?1:-1));return validQuotes;}/**
* Get quote for a single route by simulating each hop
*/privateasyncgetQuoteForRoute( chainId:number, provider: PublicClient, route: DiscoveredRoute, amountIn: bigint, toDecimals:number,):Promise<RouteQuote |null>{try{const hopsData: HopQuoteData[]=[];let currentAmount = amountIn;// Simulate each hop in sequencefor(const hop of route.hops){const hopQuote =awaitthis.getHopQuote(chainId, provider, hop, currentAmount);if(!hopQuote){returnnull;// Route fails if any hop fails} hopsData.push(hopQuote); currentAmount = hopQuote.amountOut;// Output becomes next hop's input}return{ route, amountIn, amountOut: currentAmount, amountOutFormatted:formatUnits(currentAmount, toDecimals), priceImpact:this.estimatePriceImpact(route.totalHops, amountIn), estimatedGas: route.estimatedGas, hopsData,};}catch(error){returnnull;}}/**
* Get quote for a single hop (V2 or V3)
*/privateasyncgetHopQuote( chainId:number, provider: PublicClient, hop: RouteHop, amountIn: bigint,):Promise<HopQuoteData |null>{const dexInfo =DEX_INFO[hop.dexId];const dexContracts =DEX_CONTRACTS[chainId]?.[hop.dexId];if(dexInfo.type ==='amm_v3'){returnawaitthis.getV3HopQuote(provider, dexContracts.quoter!, hop, amountIn);}elseif(dexInfo.type ==='amm_v2'){returnawaitthis.getV2HopQuote(provider, dexContracts.router, hop, amountIn);}returnnull;}}
The hop-by-hop simulation ensures accurate output calculations for complex multi-hop routes, where each intermediate swap's output becomes the next swap's input
Price impact is estimated using heuristics based on trade size in USD. The RouteOptimizerService uses a tiered linear interpolation approach:
TypeScript
/**
* Estimate price impact based on trade size
*/estimatePriceImpact(amountUsd:number):number{if(amountUsd <1000)return0.1;if(amountUsd <10000)return0.2+(amountUsd -1000)*0.00001;if(amountUsd <100000)return0.3+(amountUsd -10000)*0.000005;if(amountUsd <1000000)return0.8+(amountUsd -100000)*0.000002;return3;// Cap at 3%}
The final quote response provides everything the frontend needs:
TypeScript
interfaceFeeBreakdown{ broadcasterFee:string;// Gas cost + 15% margin (in WETH) voidDexFee:string;// 0.05% of input (in WETH) totalFeeWeth:string;// Sum of all fees (in WETH) voidDexFeeBps:number;// Fee rate in basis points (5)}interfaceQuoteResponse{ fromToken:string; toToken:string; fromAmount:string; toAmount:string; route:{ steps: RouteStep[]; totalSteps:number; isSplit:boolean; isSequential?:boolean;}; fees: FeeBreakdown; meta:{ priceImpact:string; exchangeRate:string; minReceived:string; expiresAt:number;// Unix timestamp};}// Example response{"fromToken":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","toToken":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","fromAmount":"1000000000000000000","toAmount":"3245670000","route":{"steps":[{"dexId":"uniswap-v3","percentage":10000,"minAmountOut":"3245670000","dexData":{"feeTier":500}}],"totalSteps":1,"isSplit":false},"fees":{"broadcasterFee":"2300000000000000","voidDexFee":"500000000000000","totalFeeWeth":"2800000000000000","voidDexFeeBps":5},"meta":{"priceImpact":"0.05","exchangeRate":"3245.67","minReceived":"3229.45","expiresAt":1705430400000}}
The example response shows a single-DEX route using Uniswap V3. The fees are denominated in WETH (wei units), with the broadcaster fee covering gas costs plus a 15% profit margin.
The frontend uses this to display the route visualization, show the user what they'll receive, and encode the on-chain transaction.
How VoidDex implements private token swaps using Railgun's zero-knowledge proof system, covering the shield/unshield flow, client-side proof generation, and the Waku P2P broadcaster network.
Every swap you make on a decentralized exchange is public. VoidDex is my attempt at building a DEX aggregator that maintains the benefits of decentralized trading while adding a cryptographic privacy layer using zkSNARK proofs.