> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lunya.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Swaps & Routing

> The router, the quoter, path encoding, and the pool-level swap

## Path encoding

A route is bytes: `tokenA | poolType | tokenB | poolType | tokenC | …` — addresses of 20 bytes joined by a **single byte of pool type** where Uniswap V3 has a three-byte fee tier. The route names the curve, not the price: fees here are governance state a plugin can override per swap, so a fee-bearing route would name a pool that stops existing the moment a fee moved.

Pool type values: `CL = 0`, `CP = 1`, `STABLE = 2` — [an open set](/developers/dex/pools-and-discovery#pool-types-are-an-open-set).

<Warning>
  **Exact-output paths are encoded in reverse** — token bought first, token sold last — exactly as in Uniswap V3. Encoding them forwards silently prices the wrong trade.
</Warning>

## The router

`SwapRouter` exposes the four V3-shaped entries; the difference from V3 is the `poolType` field where `fee` was:

```solidity theme={null}
struct ExactInputSingleParams {
    address tokenIn; address tokenOut; PoolType poolType;
    address recipient; uint256 deadline;
    uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata) external payable returns (uint256 amountOut);

struct ExactInputParams {
    bytes path; address recipient; uint256 deadline;
    uint256 amountIn; uint256 amountOutMinimum;
}
function exactInput(ExactInputParams memory) external payable returns (uint256 amountOut);

struct ExactOutputSingleParams {
    address tokenIn; address tokenOut; PoolType poolType;
    address recipient; uint256 deadline;
    uint256 amountOut; uint256 amountInMaximum; uint160 sqrtPriceLimitX96;
}
function exactOutputSingle(ExactOutputSingleParams calldata) external payable returns (uint256 amountIn);

struct ExactOutputParams {
    bytes path;              // reversed
    address recipient; uint256 deadline;
    uint256 amountOut; uint256 amountInMaximum;
}
function exactOutput(ExactOutputParams memory) external payable returns (uint256 amountIn);
```

Behavior worth knowing:

* **Deadline**: `Expired()` past it. **Slippage**: `TooLittleReceived()` / `TooMuchRequested()`.
* **Price limit**: `0` means "the widest the pool accepts" (`MIN_SQRT_RATIO+1` / `MAX_SQRT_RATIO−1`) — a real bound, not "no limit". A limit at or beyond the current price reverts `InvalidSqrtPriceLimit()`. Multi-hop entries always pass the widest per hop; there is no per-hop limit.
* **A binding limit under-delivers instead of failing** — and unlike upstream, this router checks the shortfall itself: an exact-output swap that cannot fill reverts `TooLittleReceived()` rather than silently delivering less.
* **Exact-output pulls only what the swap cost** — no ERC-20 change is left behind. Native over-send must be reclaimed by you (below).
* Amounts above `int256.max` revert `AmountOutOfRange()` — beyond it the cast flips exact-input into a monstrous exact-output.
* Gasless approvals: `selfPermit` / `selfPermitIfNecessary` on the router.

## Native coin, multicall, and the one hard requirement

The router batches via `multicall` and settles native through its own balance. Three consequences:

* **`msg.value` is not a budget**: every batched call sees the full amount. Payment logic spends the contract's actual balance, so batching stays safe — but do not meter by `msg.value`.
* **The router must hold nothing between transactions.** The sweep helpers (`refundNative`, `unwrapNative`, `sweepToken`) send the **whole balance to anyone who calls**. Therefore: *every entry point that can leave a balance must be called inside one atomic `multicall` that ends with the matching sweep.* **This is a hard requirement of the API, not a recommendation** — a payable swap sent bare, stopped at its price limit, leaves coin the next caller takes.
* **On a chain with no native wrapper** (`wrappedNative() == 0` — the Arc case, where the gas token *is* the quote ERC-20 seen at 18 decimals): `refundNative` and `unwrapNative` revert `NoNativeWrapper()`; use `sweepToken`. The router's `receive()` rejects unsolicited native.

## The quoter

```solidity theme={null}
function quoteExactInputSingle(address tokenIn, address tokenOut, PoolType poolType,
    uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut);
function quoteExactOutputSingle(address tokenIn, address tokenOut, PoolType poolType,
    uint256 amountOut, uint160 sqrtPriceLimitX96) external returns (uint256 amountIn, uint256 amountOutReceived);
function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);
function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn, uint256 amountOutReceived);
```

* **Not a `view`** — it prices by *starting* the swap and aborting it from the callback (revert-and-catch). Call it with `eth_call`; on-chain it burns a swap's gas.
* **`amountOutReceived`** on the exact-output quoters reports what would actually arrive when a price limit cuts the swap short — compare routes on it, or you will pick one that cannot fill. V3's quoter has no equivalent.
* **A route may not visit the same pool twice** (`PoolVisitedTwice()`): each simulated hop rolls back before the next, so a second visit would price against pre-trade reserves. Split such routes or price them yourself.
* **Quotes are bids, not promises**: dynamic fees price on measured volatility (and may price on the swap's own size), so a quote ages faster than on a static-fee venue.

## The pool-level swap

For integrators who settle themselves:

```solidity theme={null}
function swap(
    address recipient,
    bool zeroForOne,
    int256 amountSpecified,     // positive = exact input, negative = exact output
    uint160 sqrtPriceLimitX96,
    bytes calldata data
) external returns (int256 amount0, int256 amount1);   // deltas from the pool's view

function lunyaSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external;
```

The pool sends the output, then calls back for payment — pay the positive delta before returning. Verify the caller is a pool from the canonical factory (`getPool`, not an init-code-hash recomputation). `amountSpecified` of `0` or `int256.min` reverts on every pool type. The signature — including the price limit — is uniform across CL, CP and STABLE; a STABLE swap simply resolves in one step instead of a tick loop.

Sibling callbacks for liquidity and flash: `lunyaMintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes)` and `lunyaFlashCallback(uint256 fee0, uint256 fee1, bytes)`.
