# Liquidity provision

This page covers everything an **LP** (or an integrator acting on an LP's behalf) touches on SwapCore. Deposits mint shares against the pool's mark-to-market value, withdrawals burn them at the current share price, and a handful of view functions expose the pool's state.

Source: `SwapCore.sol`

## How the pool works

Each market has a single collateral pool (`Types.Pool`) with three numbers:

* `totalShares` — total LP shares outstanding.
* `totalCollateral` — total underlying `swapToken` the pool holds (LP-supplied + retained swap payments, minus paid-out losses).
* `lockedCollateral` — the portion of `totalCollateral` reserved against open swaps. `totalCollateral − lockedCollateral` is what can actually be withdrawn or used to back new swaps.

**Share price** is computed by `getPoolSharePrice(marketId)` by projecting each bucket's P\&L at the current oracle index and dividing the pool's fair value by `totalShares`. It is floored at `Utils.MIN_SHARE_PRICE` — an underwater pool reports the floor rather than zero so the math stays well-defined.

**Mint flow (`supplyCollateral`):** shares minted = `collateralAmount × WAD / sharePrice`, rounded down. For the first deposit (empty pool) `sharePrice` defaults to `WAD` and one token unit mints one share. Shares and `lastDepositBlock` are credited to `onBehalfOf`.

**Burn flow (`withdrawCollateral`):** shares burned = `amount × WAD / sharePrice`, rounded **up in favor of the pool**. Burning uses the caller's own LP balance (from `onBehalfOf`); tokens land on `msg.sender` so bundlers can forward.

**Two gates LPs should know about:**

1. **1-block deposit lock.** `supplyCollateral` stamps `lpPos.lastDepositBlock = block.number`, and `withdrawCollateral` reverts with `E413` if you try to withdraw in the same block. Plus, `supplyCollateral` requires auth for `onBehalfOf` even though deposits are nominally beneficial — this prevents an attacker from front-running your withdrawal with a dust deposit that would push your `lastDepositBlock` forward and DoS you for a block.
2. **Expired-unsettled gate.** Both `supplyCollateral` and `withdrawCollateral` call `_revertIfExpiredUnsettled(marketId, bucketInterval)`. If the market has any expired swaps that haven't been run through `makePayment` yet, LP operations revert with `E415`. The share price is unreliable until those swaps settle — call `makePayment(expiredSwapIds)` first (anyone can).

**MIN\_SHARE\_PRICE guard.** If the pool is at the underwater floor **and** has active swaps (`lockedCollateral > 0`), deposits revert with `E414`. At the floor, shares are massively inflated (1M:1), so a fresh deposit would capture any eventual settlement recovery at the expense of existing LPs. If there are no active swaps, deposits are allowed — there's no pending inflow to capture.

**Last-LP guard.** `withdrawCollateral` will not let `pool.totalShares` drop to zero while `lockedCollateral > 0`. The last share is retained so accounting stays alive through settlement. If you're the last LP, a full withdrawal leaves one share behind; a partial withdrawal that would otherwise burn the final share reverts with `E416`. Once all swaps have settled, a last LP can sweep the pool clean.

***

## supplyCollateral

```solidity
function supplyCollateral(
    bytes32 marketId,
    uint256 collateralAmount,
    address onBehalfOf,
    uint256 minSharesOut
) external nonReentrant;
```

**Who calls:** an LP, or an authorized delegate. Shares are credited to `onBehalfOf`, but tokens are pulled from `msg.sender` (so bundlers and routers work via Permit2-style flows).

**What it does:** refreshes the rate index, computes the current share price, mints `collateralAmount × WAD / sharePrice` shares to `onBehalfOf`, stamps `lastDepositBlock`, and pulls `collateralAmount` of `market.swapToken` from `msg.sender` into SwapCore.

**Parameters:**

| Parameter          | Meaning                                                                                                                                                                     |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `marketId`         | Target market.                                                                                                                                                              |
| `collateralAmount` | Tokens to deposit, in `swapToken` decimals. Must be `> 0`.                                                                                                                  |
| `onBehalfOf`       | The address that receives the shares. `msg.sender` must be authorized via [`setAuthorization`](/protocol/authorization.md#setauthorization) if different from `msg.sender`. |
| `minSharesOut`     | Slippage guard: if `> 0` and fewer shares would be minted, revert with `E411`. Pass `0` to disable.                                                                         |

**State changes / events:**

* `lpPositions[marketId][onBehalfOf].shares += sharesToMint`
* `lpPositions[marketId][onBehalfOf].lastDepositBlock = block.number`
* `pool.totalShares += sharesToMint`
* `pool.totalCollateral += collateralAmount`
* `IERC20(swapToken).transferFrom(msg.sender, address(this), collateralAmount)`
* Emits `CollateralSupplied(marketId, swapToken, onBehalfOf, caller, amount, sharesMinted, sharePrice)`.

**Reverts:**

| Code   | Reason                                                                                                                             |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `E206` | `msg.sender` is not authorized to act for `onBehalfOf`.                                                                            |
| `E300` | Market doesn't exist.                                                                                                              |
| `E415` | Market has expired swaps that haven't been settled — call `makePayment` first.                                                     |
| `E201` | `lpWhitelistEnabled` and `onBehalfOf` is not on `marketLpWhitelist[marketId]`.                                                     |
| `E400` | `collateralAmount == 0`, or share calculation rounds down to `sharesToMint == 0` (deposit too small for the current share price).  |
| `E414` | Pool is at `MIN_SHARE_PRICE` floor and has active swaps — deposits are blocked to prevent dilution capture of settlement recovery. |
| `E411` | Slippage — fewer shares minted than `minSharesOut`.                                                                                |

**See also:** [`withdrawCollateral`](#withdrawcollateral), [`getPoolSharePrice`](#getpoolshareprice), [`setAuthorization`](/protocol/authorization.md#setauthorization).

***

## withdrawCollateral

```solidity
function withdrawCollateral(
    bytes32 marketId,
    uint256 amount,
    bool    fullAmount,
    address onBehalfOf,
    uint256 minCollateralOut
) external nonReentrant returns (uint256 withdrawalAmount);
```

**Who calls:** an LP, or an authorized delegate of `onBehalfOf`. Tokens are always delivered to `msg.sender` (so a bundler can receive them and forward).

**What it does:** refreshes the rate index, burns enough of `onBehalfOf`'s shares to cover the requested withdrawal, and sends the underlying `swapToken` out. If `fullAmount = true`, `amount` is ignored and the LP's entire position is redeemed.

**Parameters:**

| Parameter          | Meaning                                                                                                                                                                                           |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `marketId`         | Market to withdraw from.                                                                                                                                                                          |
| `amount`           | Collateral to withdraw in `swapToken` decimals. Ignored when `fullAmount = true`.                                                                                                                 |
| `fullAmount`       | If true, redeem the LP's entire share balance in one shot.                                                                                                                                        |
| `onBehalfOf`       | Owner of the shares. `msg.sender` must be authorized via `setAuthorization` unless they are `onBehalfOf`.                                                                                         |
| `minCollateralOut` | Slippage guard: if `> 0` and the computed `withdrawalAmount` is lower, revert with `E412`. Pass `0` to disable. Especially useful with `fullAmount = true`, where you don't pass a target amount. |

**Returns:** `withdrawalAmount` — the actual amount of `swapToken` delivered.

**State changes / events:**

* `lpPositions[marketId][onBehalfOf].shares -= sharesToRedeem`
* `pool.totalShares -= sharesToRedeem`
* `pool.totalCollateral -= withdrawalAmount`
* `IERC20(swapToken).safeTransfer(msg.sender, withdrawalAmount)`
* Emits `CollateralTokenWithdrawn(marketId, swapToken, onBehalfOf, caller, amount, sharesRedeemed, sharePrice)`.

**Reverts:**

| Code   | Reason                                                                                                                                                |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `E206` | `msg.sender` not authorized for `onBehalfOf`.                                                                                                         |
| `E300` | Market doesn't exist.                                                                                                                                 |
| `E415` | Expired swaps haven't been settled yet — call `makePayment` first.                                                                                    |
| `E400` | `amount == 0 && !fullAmount`, or `sharesToRedeem` rounds to 0.                                                                                        |
| `E413` | Same-block deposit + withdraw from `onBehalfOf` (flash-loan guard).                                                                                   |
| `E407` | Pool has zero total shares.                                                                                                                           |
| `E406` | Requested partial withdrawal needs more shares than `onBehalfOf` owns.                                                                                |
| `E405` | Computed `withdrawalAmount` exceeds `totalLPAvailableCollateral(marketId)` — the pool doesn't have enough unlocked collateral.                        |
| `E412` | Slippage — `withdrawalAmount < minCollateralOut`.                                                                                                     |
| `E416` | You're the last LP, you'd burn the final share, and `lockedCollateral > 0` — a single share is retained so accounting stays alive through settlement. |

**See also:** [`supplyCollateral`](#supplycollateral), [`totalLPAvailableCollateral`](#totallpavailablecollateral), [`getLpValue`](#getlpvalue).

***

## getLpPosition

```solidity
function getLpPosition(bytes32 marketId, address account)
    external view returns (Types.LpPosition memory);
```

**Who calls:** anyone — UIs, indexers, adapters.

**What it does:** returns the full `LpPosition` struct for `account`:

```solidity
struct LpPosition {
    uint256 shares;            // pool shares held
    uint256 lastDepositBlock;  // block of most recent supplyCollateral
}
```

Reverts with `E300` if the market doesn't exist.

***

## getLpShares

```solidity
function getLpShares(bytes32 marketId, address lpAddress)
    external view returns (uint256);
```

Thin accessor — returns just `lpPositions[marketId][lpAddress].shares`. Does not revert on a non-existent market; it will simply return `0`. Use this when you only need the share count and want to avoid the `getLpPosition` tuple.

***

## getLpValue

```solidity
function getLpValue(bytes32 marketId, address lpAddress)
    external view returns (uint256);
```

Returns the mark-to-market value of `lpAddress`'s shares, denominated in `swapToken` decimals:

```
value = shares × getPoolSharePrice(marketId) / WAD
```

Useful for showing LPs their live NAV in a UI. Because it calls `getPoolSharePrice`, it will project the oracle index forward and reflect unrealized swap P\&L. The share price is floored at `MIN_SHARE_PRICE`, so an underwater pool still returns a positive number.

***

## isUserLpInMarket

```solidity
function isUserLpInMarket(bytes32 marketId, address userAddress)
    external view returns (bool);
```

Returns `true` if `userAddress` has any shares in `marketId`. Reverts with `E300` if the market doesn't exist.

***

## getPoolSharePrice

```solidity
function getPoolSharePrice(bytes32 marketId) public view returns (uint256);
```

Returns the mark-to-market share price in WAD (`1e18 = 1.0`). If the pool has `totalShares == 0`, returns `WAD` as a safe default (prevents manipulation of the first-deposit ratio). Otherwise it projects a fresh oracle index without mutating state and iterates over the market's buckets via `Utils.calculateLpSharePrice` to compute the fair value.

The result is floored at `Utils.MIN_SHARE_PRICE`. If the pool is deeply underwater, `getPoolSharePrice` will report the floor — consumers should treat a floor reading as "pool is distressed, deposits will be blocked if `lockedCollateral > 0`" rather than as an accurate valuation.

Called internally by `supplyCollateral`, `withdrawCollateral`, and `getLpValue`. Safe to call off-chain.

***

## getPoolMetrics

```solidity
function getPoolMetrics(bytes32 marketId)
    external view returns (uint256 totalShares, uint256 totalCollateral, uint256 lockedCollateral);
```

One-shot read of the three numbers that make up `Types.Pool`. `totalCollateral − lockedCollateral` is the unlocked balance available for new swaps or LP withdrawals; the dedicated [`totalLPAvailableCollateral`](#totallpavailablecollateral) helper returns the same subtraction.

***

## totalLPAvailableCollateral

```solidity
function totalLPAvailableCollateral(bytes32 marketId) public view returns (uint256);
```

Returns `pool.totalCollateral − pool.lockedCollateral` — the amount of `swapToken` in the pool that is **not** currently backing an open swap. This is the number that both `withdrawCollateral` and `buySwap` check against before committing state. Pair it with [`getCalculatedAvailableLiquidity`](/protocol/views.md#getcalculatedavailableliquidity) to convert "unlocked collateral" into "maximum notional a new swap can use".


---

# 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.kairosswap.com/protocol/liquidity.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.
