# FPMM

The **FPMM** contract is the core pool of Mento V3. Each instance holds two ERC-20 tokens (reserves), executes swaps at the **oracle rate** (minus fees), and allows **rebalancing** only by allowlisted liquidity strategies when the reserve-implied price deviates from the oracle by at least a configured threshold.

**Contract:** [mento-protocol/mento-core](https://github.com/mento-protocol/mento-core) — `contracts/swap/FPMM.sol`

***

## Interacting with the pool (code examples)

The snippets below show how to use the FPMM from Solidity. You need the **pool address** (from the factory or deployments) and the **token addresses** (pool uses sorted order: `token0` < `token1`). For most app flows, the [Router](/mento-v3/build/smart-contracts/router.md) or the [Mento SDK](/mento-v3/build/mento-sdk.md) simplify quoting and execution.

### Finding the pool

Get the pool for a token pair from the factory (tokens are sorted by address):

```solidity
IFPMMFactory factory = IFPMMFactory(factoryAddress);
(address token0, address token1) = factory.sortTokens(usdc, usdm);
address pool = factory.getPool(token0, token1);
// pool == address(0) if not deployed yet
```

### Quoting a swap

Get the expected amount of output token for a given amount in. The quote uses the **oracle rate** minus fees. Reverts if the oracle is invalid (e.g. market closed, breaker tripped, stale).

```solidity
IFPMM pool = IFPMM(poolAddress);
uint256 amountIn = 1_000_000; // 1 USDC (6 decimals)
uint256 amountOut = pool.getAmountOut(amountIn, usdc);
// amountOut is in output token decimals (e.g. USDm)
```

### Executing a swap (via Router)

The Router handles token transfers and calls the pool’s `swap` with the correct output amount. **Approve the router** to spend `amountIn` of the input token, then:

```solidity
IRouter router = IRouter(routerAddress);
IRouter.Route[] memory routes = new IRouter.Route[](1);
routes[0] = IRouter.Route({ from: usdc, to: usdm, factory: address(0) });

uint256[] memory amounts = router.swapExactTokensForTokens(
    amountIn,
    amountOutMin,   // slippage: minimum amount out (e.g. amountOut * 99 / 100)
    routes,
    msg.sender,
    block.timestamp + 300
);
// amounts[0] == amountIn, amounts[1] == actual amount out
```

### Executing a swap (direct pool call)

The pool’s `swap(amount0Out, amount1Out, to, data)` is **output-specified**. The caller must ensure the pool receives the **input** tokens (e.g. by implementing `IFPMMCallee` and passing `data` so the pool calls back into your contract, which then transfers the input tokens). For simple EOA flows, use the Router instead.

### Checking rebalance state

Useful for keepers or UIs to see if the pool is eligible for rebalance and the current deviation:

```solidity
IFPMM pool = IFPMM(poolAddress);
(
    uint256 oraclePriceNumerator,
    uint256 oraclePriceDenominator,
    uint256 reservePriceNumerator,
    uint256 reservePriceDenominator,
    bool reservePriceAboveOraclePrice,
    uint16 rebalanceThreshold,
    uint256 priceDifference
) = pool.getRebalancingState();
// Rebalance is allowed only when priceDifference >= rebalanceThreshold
```

### Adding and removing liquidity

Mint: transfer **both** tokens to the pool in the **current reserve ratio**, then call `mint(to)`. Burn: transfer LP tokens to the pool, then call `burn(to)`.

```solidity
// Mint: ensure pool has received token0 and token1 in correct ratio
IERC20(pool.token0()).transfer(pool, amount0);
IERC20(pool.token1()).transfer(pool, amount1);
uint256 liquidity = pool.mint(msg.sender);

// Burn: transfer LP tokens to pool first
IERC20(pool).transfer(pool, liquidity);
(uint256 amount0, uint256 amount1) = pool.burn(msg.sender);
```

***

## Invariants (design)

The contract enforces:

1. **Swap** does not decrease the total value of the pool (value at oracle, after crediting fees).
2. **Rebalance** reduces the price difference and keeps the same direction (above oracle stays above, below stays below).
3. **Rebalance** keeps the post-rebalance price difference at or above the configured threshold (no overshoot past the band).
4. **Rebalance** does not decrease reserve value more than the rebalance incentive (minimum repayment / incentive cap).

***

## State (FPMMStorage)

| Field                                                | Meaning                                                                                                                           |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `token0`, `token1`                                   | Pool tokens, ordered by address (token0 < token1).                                                                                |
| `decimals0`, `decimals1`                             | Scaling: `10^decimals` for each token (capped at 18).                                                                             |
| `reserve0`, `reserve1`                               | Last known reserve balances (updated after swap/mint/burn/rebalance).                                                             |
| `oracleAdapter`                                      | Contract used for the oracle rate; pool calls `getFXRateIfValid(referenceRateFeedID)`.                                            |
| `referenceRateFeedID`                                | Rate feed ID passed to the oracle adapter (e.g. address derived from pair identifier).                                            |
| `invertRateFeed`                                     | If true, the adapter rate is inverted (denominator/numerator) for quoting.                                                        |
| `lpFee`, `protocolFee`                               | Swap fees in basis points (BPS, denominator 10,000). Combined max 200 (2%).                                                       |
| `protocolFeeRecipient`                               | Receives protocol fee.                                                                                                            |
| `feeSetter`                                          | Address (with owner) allowed to set fees.                                                                                         |
| `rebalanceIncentive`                                 | Max incentive in BPS (max 100 = 1%) for rebalance; enforces minimum token-in.                                                     |
| `rebalanceThresholdAbove`, `rebalanceThresholdBelow` | Min price difference (BPS) to allow rebalance when reserve price > oracle or < oracle. Above max 10,000 bps; below max 5,000 bps. |
| `liquidityStrategy[addr]`                            | If true, `addr` may call `rebalance`.                                                                                             |
| `tradingLimits[token]`                               | Per-token TradingLimitsV2 config and state (5-min and 1-day net-flow caps).                                                       |

***

## Initialization

`initialize(token0, token1, oracleAdapter, referenceRateFeedID, invertRateFeed, initialOwner, params)` sets tokens, oracle adapter, rate feed ID, invert flag, owner, and `FPMMParams` (fees, fee recipient, fee setter, rebalance incentive, rebalance thresholds). Tokens are **not** re-sorted in `initialize`; the factory passes them already sorted. Token decimals must be ≤ 18.

***

## Oracle rate

The pool uses **oracle rate** for all swap quotes and value checks. It does **not** use a reserve-based curve.

* **Source:** `_getRateFeed()` calls `oracleAdapter.getFXRateIfValid(referenceRateFeedID)`. If the adapter reverts (e.g. `FXMarketClosed`, `TradingSuspended`, `NoRecentRate`), the pool reverts.
* **Invert:** If `invertRateFeed` is true, the rate is swapped to (denominator, numerator) so that the pool’s quote direction matches the intended token pair (e.g. token1/token0).

***

## Swap

* **Quote:** `getAmountOut(amountIn, tokenIn)` returns the amount of the other token at the **oracle rate** minus combined fee:\
  `rate × amountIn × (BPS - lpFee - protocolFee) / BPS`, with decimal scaling.\
  Reverts if the adapter’s `getFXRateIfValid` fails.
* **Execution:** `swap(amount0Out, amount1Out, to, data)` is **output-specified**: exactly one of `amount0Out` or `amount1Out` must be > 0. The pool transfers the output to `to`, then, if `data.length > 0`, calls `IFPMMCallee(to).hook(...)` so the caller can supply the input tokens. The pool infers `amount0In` and `amount1In` from balance deltas.
* **Checks:**
  1. **Value protection:** `_swapCheck`: reserve value at oracle (in token1, 1e18-scaled) after the swap must be ≥ initial value + fee value; otherwise `ReserveValueDecreased`.
  2. **TradingLimitsV2:** After the swap, `_applyTradingLimits` is called for each token (fee is deducted from inflow before net flow). If a limit is exceeded, the swap reverts.
* **Fees:** Protocol fee is transferred to `protocolFeeRecipient`; LP fee remains in the pool (increases reserve value). Reserves are synced with `_update()`.

***

## Mint and burn

* **Mint:** `mint(to)` — Caller must have transferred token0 and token1 to the pool in the **current reserve ratio**. Liquidity is computed: first mint `sqrt(amount0*amount1) - MINIMUM_LIQUIDITY` (with `MINIMUM_LIQUIDITY` locked at `address(1)`); subsequent mints proportional to reserves. Shares are minted to `to`.
* **Burn:** `burn(to)` — Caller must have transferred LP tokens to the pool. Burn is pro-rata: amounts out = (liquidity / totalSupply) × reserves; tokens sent to `to`, reserves updated.

***

## Rebalance

* **Entrypoint:** `rebalance(amount0Out, amount1Out, data)`. Only callable by an address with `liquidityStrategy[msg.sender] == true`.
* **Preconditions:**
  * Exactly one of `amount0Out`, `amount1Out` > 0.
  * Reserve price deviation from oracle ≥ threshold (above or below depending on direction).
  * Pool has sufficient liquidity for the requested output.
* **Flow:**
  1. Pool transfers the requested token(s) to `msg.sender` (the strategy).
  2. Pool calls `ILiquidityStrategy(msg.sender).onRebalance(msg.sender, amount0Out, amount1Out, data)`.
  3. Strategy must send back the **other** token (and optionally keep an incentive within the cap).
  4. Pool infers `amount0In`, `amount1In` from balance deltas.
  5. `_rebalanceCheck(swapData)` enforces: price difference **improves**, **direction preserved**, no overshoot (new difference ≥ threshold), and minimum repayment (incentive cap).
* **Rebalancing state:** `getRebalancingState()` returns oracle price, reserve price, whether reserve price is above oracle, the applicable threshold, and current price difference in BPS. Off-chain or strategy logic can use this to decide rebalance size.

***

## TradingLimitsV2

* **Config:** `configureTradingLimit(token, config)` (owner) sets the TradingLimitsV2 config for that token (e.g. 5-min and 1-day limits).
* **Application:** After every swap, `_applyTradingLimits(token0, ...)` and `_applyTradingLimits(token1, ...)` update state and revert if a limit is exceeded. Fee is deducted from inflow before net flow.
* **View:** `getTradingLimits(token)` returns config and state for the token.

***

## Admin

| Function                                                          | Restriction     | Effect                                 |
| ----------------------------------------------------------------- | --------------- | -------------------------------------- |
| `setLPFee`, `setProtocolFee`                                      | `onlyFeeSetter` | Combined with the other fee ≤ 200 bps. |
| `setProtocolFeeRecipient`                                         | owner           |                                        |
| `setFeeSetter`                                                    | owner           |                                        |
| `setRebalanceIncentive`                                           | `onlyFeeSetter` | Max 100 bps.                           |
| `setRebalanceThresholds(above, below)`                            | owner           | Above ≤ 10,000 bps; below ≤ 5,000 bps. |
| `setOracleAdapter`, `setReferenceRateFeedID`, `setInvertRateFeed` | owner           |                                        |
| `setLiquidityStrategy(addr, allowed)`                             | owner           | Allowlist for `rebalance`.             |
| `configureTradingLimit(token, config)`                            | owner           | TradingLimitsV2 per token.             |

***

## Key errors

* `ReserveValueDecreased` — Swap would decrease pool value at oracle after fees.
* `NotLiquidityStrategy` — Caller of `rebalance` not allowlisted.
* `PriceDifferenceTooSmall` — Rebalance called but deviation below threshold.
* `PriceDifferenceNotImproved`, `PriceDifferenceMovedInWrongDirection`, `PriceDifferenceMovedTooFarFromThresholds` — Rebalance acceptance failed.
* `InsufficientAmount0In` / `InsufficientAmount1In` — Strategy did not return enough of the other token (incentive cap).
* `TradingSuspended`, `NoRecentRate`, `FXMarketClosed` — From oracle adapter when rate is invalid.

***

## See also

* [FPMMFactory](/mento-v3/build/smart-contracts/fpmmfactory.md) — Deployment and default params.
* [OracleAdapter](/mento-v3/build/smart-contracts/oracleadapter.md) — How the pool gets the rate.
* [TradingLimitsV2](/mento-v3/build/smart-contracts/tradinglimits.md) — Net-flow caps.
* [Liquidity strategies](/mento-v3/build/smart-contracts/liquidity-strategies.md) — Who can call rebalance and how.
* [Dive Deeper: FPMMs](/mento-v3/dive-deeper/fpmm.md) — Design and invariant.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mento.org/mento-v3/build/smart-contracts/fpmm.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
