# Router

The router improves the discoverability of offers made by **makers** for **takers**. Because offers are published to a **public**, **uncensored mempool**, the router ensures malicious or invalid offers are filtered out before they reach takers, acting as an opinionated infrastructure that lets applications be built without needing custom offer validation or anti-spam logic.

It follows a set of rules managed by an external service, the **gatekeeper**, exposed at **`GET /v1/config/rules`**. Those rules determine which offers are fully excluded, based on criteria such as offer format, tokens, maturity, callback, and more. Any maker that wants to comply with these rules can call **`POST /v1/validate`** to confirm that their offers are supported by the router.

### The mempool

Although the mempool is unrestricted, the router supports only a specific offer format. The best way for a maker to stay router-compatible is to call **`POST /v1/validate`** to get the **encoded offer payload**, sign the returned **root**, and publish the payload with the **signature appended** (`data = payload + signature`).

The maker can then be confident their offers will be indexed correctly when queried.

```text
POST /v1/validate --data "{ chainId, offers: [...] }"
 → { payload, root }
 → sign(root)
 → publish(payload + signature)
```

Offers are submitted as a batch using a Merkle tree, so one signature can cover multiple offers. The Merkle root is a compact
commitment to the whole set: any change to any offer changes the root, invalidating the signature. To reduce calldata (and gas)
usage, the full offer set is embedded in the payload as gzipped data instead of plain JSON.

## Router takeable computation

Publishing an offer to the mempool (even with a supported format) does **not** guarantee that it is currently takeable. A core responsibility of the router is to compute the **takeable** amount for each offer: the maximum amount that can be executed *without reverting*, given current state.

The router computes `takeable = min(availableLiquidity, offerRemaining)`.

`offerRemaining` is the remaining, unconsumed portion of the offer (i.e., the total offered amount minus what has already been taken by other takers).

`availableLiquidity` is the amount of liquidity the router can safely source for the offer at execution time. It is derived from the offer’s **callback**.

A **callback** is the on-chain "liquidity source" used during settlement: it tells the protocol *where* to pull assets from (e.g., the maker’s wallet, or a specific vault position) when the offer is filled. Callbacks are extremely flexible, but that flexibility also means the router cannot reliably compute takeability for arbitrary callback logic.

To keep takeability deterministic and indexable, the router only supports **empty callbacks** with well-defined semantics:

- **Buy with empty callback** (`BuyWithEmptyCallback`): source the buy-side liquidity from the maker's ERC20 balance/allowance. The callback address is the zero address and data is `0x`.
- **Sell with empty callback** (`SellWithEmptyCallback`): sell-side offers with empty callback data. The callback address is the zero address and data is `0x`.

In practice, the router indexes the underlying source (wallet balances/allowances) and caps the offer by what is actually available. If an offer uses a non-empty callback (non-zero address or non-`0x` data), it will be rejected by the router's gatekeeper (see **`POST /v1/validate`**).

If `availableLiquidity = 0`, the offer is **not** treated as invalid. The offer remains indexed, but it is returned with `takeable = 0` (and therefore will not be executed by router consumers).

### Takeable by user intent

A "user intent" is the user-facing action an offer is meant to execute (e.g., enter supply, enter borrow, exit supply/withdraw, or exit borrow/repay). The router accounts for intent when computing takeable because each intent has different execution constraints and denominations (assets vs shares vs obligation units) and can rely on different sources of liquidity; as a result, the safe amount that can be taken without reverting must be capped using intent-specific rules.

#### Main: Enter supply

For *Enter supply*, the offer must be denominated in **assets**. The router decodes the callback to identify the liquidity source (the "position"), indexes the position balance, and caps the offer amount by the indexed balance.

#### Main: Enter borrow

For *Enter borrow*, the offer must be denominated in **assets**. The router evaluates takeable by inspecting the callback "position" (the same mechanism as **Enter supply**) and does **not** take Morpho V2 collateral or health into account when computing takeable; the callback is responsible for enforcing its own safety constraints. As a result, collateral already deposited on Morpho V2 does not increase borrow takeable. If a user wants to leverage more of their Morpho V2 collateral, they must withdraw that collateral and use it in a callback.

#### Secondary: exit supply (withdraw)

For *Exit supply (withdraw)*, the offer must be denominated in **shares**. The router checks the share balance on Morpho V2 and caps the offer amount by the share balance.

#### Secondary: exit borrow (repay)

For *Exit borrow (repay)*, the offer must be denominated in **obligation units**. The router checks the outstanding debt on Morpho V2.

### Overcommitting protection

When multiple offers rely on the same liquidity source, it is possible to **overcommit** liquidity (i.e., promise the same units of liquidity to multiple offers). Without protection, executing one offer can make another offer in the same batch invalid, causing **unpredictable batch failures**. The router’s goal is to make taker execution as reliable as possible, especially for **batched** execution (multicalls).

The router enforces a simple rule: **liquidity promised by a group of offers is reserved and cannot be promised twice**. For example, if a Vault V1 position has 80 units available and Offer A reserves 50 units, then a second offer attempting to reserve 50 units from the same position will only see 30 units as available and will be capped accordingly.

The same concept applies to exit intents (shares and debt): the router prevents overcommitting those balances as well.

To get the amount already reserved on a position you can query the user’s positions via `/v1/users/[:userAddress]/positions`.
