Skip to main content

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:

  1. QSN/WETH at the 0.30% standard fee tier
  2. 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:

FunctionDescription
swapExactTokensForTokensFixed input amount, minimum output
swapTokensForExactTokensMaximum input, fixed output amount
swapExactETHForTokensFixed ETH input, auto-wraps to WETH
swapTokensForExactETHFixed ETH output, auto-unwraps from WETH
swapExactTokensForETHFixed token input, output in native ETH
swapETHForExactTokensFixed ETH output target, auto-wrap input
*SupportingFeeOnTransferTokensVariants 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.