Multi-Hop Routing
QsnDEX supports multi-hop swap routing, allowing trades between tokens that do not share a direct liquidity pool. The backend routing engine discovers optimal paths, and the Router contract executes them atomically on-chain.
Path Discovery
When a user requests a swap from token A to token B, the backend evaluates available pools to find the most efficient route. If no direct A/B pool exists, the engine constructs a multi-hop path through intermediate tokens.
Example:
QSN ---> WETH ---> USDC
Pool 1 (0.30%) Pool 2 (0.05%)
In this case, swapping QSN to USDC traverses two pools:
- QSN/WETH at the 0.30% standard fee tier
- WETH/USDC at the 0.05% correlated fee tier
Each hop in the path can use a different fee tier, and the fee array is specified per hop in the transaction calldata.
Swap Variants
The Router contract exposes several swap functions to cover different use cases:
| Function | Description |
|---|---|
swapExactTokensForTokens | Fixed input amount, minimum output |
swapTokensForExactTokens | Maximum input, fixed output amount |
swapExactETHForTokens | Fixed ETH input, auto-wraps to WETH |
swapTokensForExactETH | Fixed ETH output, auto-unwraps from WETH |
swapExactTokensForETH | Fixed token input, output in native ETH |
swapETHForExactTokens | Fixed ETH output target, auto-wrap input |
*SupportingFeeOnTransferTokens | Variants that handle tokens with transfer taxes |
ETH wrapping: Functions that accept or return native ETH automatically wrap/unwrap via the WETH contract. Users do not need to manually interact with the WETH contract.
Fee-on-transfer tokens: Dedicated swap variants measure the actual token balance received by the Pair contract rather than relying on the stated transfer amount. This correctly handles tokens that deduct a fee on every transfer.
Amount Calculation
The QsnLibrary provides two key functions for computing swap amounts across multi-hop paths:
getAmountsOut(amountIn, path, fees, pairTypes)
Given an exact input amount, computes the output amount for each hop along the path. Returns an array of amounts where the last element is the final output.
getAmountsIn(amountOut, path, fees, pairTypes)
Given a desired output amount, computes the required input amount for each hop in reverse. Returns an array of amounts where the first element is the required input.
Both functions iterate through the path array, applying the appropriate AMM formula (constant product or StableSwap) and fee tier at each hop.
Deterministic Pair Addresses
Because pools are deployed via CREATE2, the address of any Pair contract can be computed off-chain using:
- The Factory contract address
- The two token addresses (sorted)
- The pool type
- The Pair contract init code hash
This means the Router and Library contracts never need to make an external call to the Factory to look up a pair address. The pairFor function in the Library computes addresses purely through hashing, saving gas on every swap.
pairAddress = CREATE2(
factory,
keccak256(abi.encodePacked(token0, token1, pairType)),
initCodeHash
)
Deadline Protection
All Router swap functions accept a deadline parameter (Unix timestamp). If the transaction is mined after the deadline, it reverts. This protects users from having stale transactions executed at unfavorable prices during periods of high network congestion.