> For the complete documentation index, see [llms.txt](https://docs.metric.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.metric.xyz/reference/smart-contracts/swapping-via-router.md).

# Swapping via router

Execute swaps through MetricOmmSimpleRouter without implementing a callback.

`MetricOmmSimpleRouter` executes ERC-20 swaps through one or more pools and handles the settlement callback. Every swap entrypoint (`exact*` and `externalSwap`) has amount-based slippage protection (`amountOutMinimum` / `amountInMaximum`) and a `deadline`. The `multicall`, `sequence`, `selfPermit*` and payment helpers take no deadline.

Routes use ERC-20 token addresses. For WETH input, the router can wrap supplied native ETH. It also provides `unwrapWETH9`, `sweepToken` and `refundETH` payment helpers. For quotes, use `MetricOmmSwapQuoter`.

### Prerequisites

* When paying from your ERC-20 balance, `approve` the router to spend `tokenIn`; its callback transfers the input during settlement.
* Optional: the router supports **`selfPermit`** and **`multicall`**, so you can permit + swap in one transaction (no separate approval tx).

### Entrypoints

```solidity
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
function exactInput(ExactInputParams calldata params)             external payable returns (uint256 amountOut);
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
function exactOutput(ExactOutputParams calldata params)             external payable returns (uint256 amountIn);
```

Single-hop params (exact input shown; exact output mirrors it with `amountOut` + `amountInMaximum`):

```solidity
struct ExactInputSingleParams {
    address pool;
    address tokenIn;          // the router pulls THIS from you in the callback; set it correctly
    address tokenOut;         // informational only; not checked onchain
    bool    zeroForOne;       // true = token0 → token1
    uint128 amountIn;
    uint128 amountOutMinimum; // slippage floor; reverts InsufficientOutput if not met
    address recipient;        // receives the output token
    uint256 deadline;         // reverts TransactionExpired once passed
    uint128 priceLimitX64;    // Q64.64 marginal bound (see sentinels below)
    bytes   extensionData;    // forwarded to the pool's swap extension; "0x" if unused
}
```

**Returns:** exact-input entrypoints return `amountOut`; exact-output entrypoints return `amountIn` (both `uint256`).

### priceLimitX64 (single-hop)

A Q64.64 marginal bound. The canonical no-limit values are:

| `zeroForOne` | Direction                | No-limit sentinel                 |
| ------------ | ------------------------ | --------------------------------- |
| `true`       | price down (lower bound) | `0`                               |
| `false`      | price up (upper bound)   | `type(uint128).max` (`2^128 − 1`) |

The router normalizes an opposite-direction open sentinel: `2^128 − 1` becomes `0` when `zeroForOne = true`; `0` becomes `2^128 − 1` when false.

Multihop entrypoints **omit per-hop price limits** (each hop runs open); slippage is controlled solely by `amountOutMinimum` / `amountInMaximum`.

### Multihop

`exactInput` / `exactOutput` take a route:

* `tokens[]`: the path `[t0, t1, …, tn]`.
* `pools[]`: pool between each adjacent pair (`length = tokens.length − 1`).
* `extensionDatas[]`: per-pool payload (`length = pools.length`).
* `zeroForOneBitMap`: bit `i` is the direction for `pools[i]` (up to 256 hops).

Exact-output routes execute **last hop → first**; `amountOut` is `tokens[last]`.

{% hint style="warning" %}
The router checks route array lengths and the hop limit, and rejects pools not registered with its factory. **It does not check that each pool connects its adjacent path tokens or that supplied `tokenIn`/`tokenOut` match pool immutables.** If they don't line up, the swap can revert, deliver a different token than you meant, or leave tokens in the router, where anyone can take them with `sweepToken`. Resolve pools and token addresses from the [metadata endpoint](/reference/api/metadata.md).
{% endhint %}

### externalSwap

```solidity
function externalSwap(address executor, bool payerIsRouter, bool refundAsNative, ExternalSwapParams calldata params)
    external payable returns (uint256 amountOut, uint256 amountSpent);
```

Swaps through a non-Metric router. The router sends `amountInMaximum` of `tokenIn` to `executor` (use the deployed `ExternalSwapExecutor`), which approves `externalRouter` and calls it with `externalRouterCalldata`. `amountOut` is the increase in `recipient`'s `tokenOut` balance; it reverts `ExternalSwapInsufficientOutput` below `amountOutMinimum`. Unused input is refunded to the caller, as ETH when `refundAsNative` is set (WETH input only). With `payerIsRouter`, the input comes from the router's own balance and unused input stays there.

### sequence

```solidity
function sequence(SequenceCall[] calldata calls)
    external payable returns (bytes[] memory results, StepStatus[] memory statuses);
```

Runs router calls in order. Each step sets what happens next: after success, `CONTINUE` or `STOP`; after failure, `CONTINUE`, `STOP` or `REVERT` (the whole sequence reverts `SequenceFailed`). A failed step is rolled back, so `msg.value` stays available to the next one. Use it for fallbacks: try pool A, then pool B, then an `externalSwap`, stopping at the first success.

### Notes

* **Settlement is internal:** the router's callback pulls `tokenIn` from you and the pool sends output to `recipient`. It validates the callback caller (`InvalidCallbackCaller`).
* **Errors:** `TransactionExpired`, `InsufficientOutput` (exact-in min), `InputTooHigh` (exact-out max), `InvalidOutputAmount`, `InvalidPool`, `InvalidPath`, `InvalidSwapDeltas`, `AmountTooLarge`, and the per-hop `InvalidInputAmountAtHop` / `InvalidOutputAmountAtHop`. Pool-level reverts (`BidIsZero`, `BidGreaterThanAsk`, `PoolPaused`, `AmountScaledExceedsMax`) bubble up. A Price Provider revert arrives wrapped as `PriceProviderFailed(bytes reason)`, e.g. `PriceProviderFailed(FeedStalled())`.

{% hint style="warning" %}
Execution uses the onchain provider quote; see [Liquidity and bins](/how-pools-work/liquidity-and-bins.md#execution-price) for pool pricing. On Ethereum, where to send the swap matters; see [Freshness and ordering](/how-pools-work/freshness-and-ordering.md#for-integrators).
{% endhint %}
