# 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**. Shares are burned from `onBehalfOf`'s balance (caller must be `onBehalfOf` or authorized); tokens are delivered to `receiver`.

**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, market.swapTerm)`. 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, 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`.                                                                                                          |
| `E728` | `oracleIndex <= SwapFormulas.MIN_INDEX` .Refuses LP deposits when the oracle index is at the floor; only hits SpotRate/SpotCompoundRate markets in practice. |

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

***

## withdrawCollateral

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

**Who calls:** an LP, or an authorized delegate of `onBehalfOf`. Shares are burned from `onBehalfOf`'s balance; the redeemed `swapToken` is delivered to `receiver` (any non-zero address — typically `onBehalfOf`, or a bundler/router that passes itself as `receiver` and forwards).

**What it does:** refreshes the rate index, burns enough of `onBehalfOf`'s shares to cover the requested withdrawal, and sends the underlying `swapToken` out. Passing `amount >= the LP's current position value` (idiomatically `type(uint256).max`) triggers a full withdrawal.&#x20;

**Parameters:**

| Parameter          | Meaning                                                                                                                                                                                                                            |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `marketId`         | Market to withdraw from.                                                                                                                                                                                                           |
| `amount`           | Collateral to withdraw in `swapToken` decimals. Ignored when `fullAmount = true`.                                                                                                                                                  |
| `receiver`         | Address that receives the withdrawn `swapToken`. Must be non-zero.                                                                                                                                                                 |
| `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 when passing `type(uint256).max` for `amount`, where you can't predict the exact payout upfront. |

**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(receiver, withdrawalAmount)`
* Emits `CollateralTokenWithdrawn(marketId, onBehalfOf, caller, receiver, amount, sharesRedeemed, sharePrice)`.

**Reverts:**

| Code   | Reason                                                                                                                                                |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `E103` | `receiver == address(0)`                                                                                                                              |
| `E206` | `msg.sender` not authorized for `onBehalfOf`.                                                                                                         |
| `E300` | Market doesn't exist.                                                                                                                                 |
| `E400` | `amount == 0`, or `sharesToRedeem` rounds to 0.                                                                                                       |
| `E413` | Same-block deposit + withdraw from `onBehalfOf` (flash-loan guard).                                                                                   |
| `E407` | Pool has zero total shares.                                                                                                                           |
| `E405` | Computed `withdrawalAmount` exceeds `totalLPAvailableCollateral(marketId)` — the pool doesn't have enough unlocked collateral.                        |
| `E412` | Slippage — `withdrawalAmount < minCollateralOut`.                                                                                                     |
| `E415` | Expired swaps haven't been settled yet — call `makePayment` first.                                                                                    |
| `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.

***

## 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.

***

## getPoolSharePriceVirtual

```solidity
function getPoolSharePriceVirtual(bytes32 marketId) external view returns (uint256);
```

Returns the LP share price in WAD (1e18 = 1.0) after virtually settling all expired-unsettled swaps at the queue head. Unlike `getPoolSharePrice`, which can read stale when expired swaps haven't been run through `makePayment` yet, this function walks the `expiryQueue` from the current pointer forward, simulates the settlement of each expired-but-unsettled swap (rolling its P\&L into the pool), and prices shares against the resulting projected pool value.

If there are no expired-unsettled swaps, it transparently falls back to `getPoolSharePrice(marketId)`.

This is the value UIs should display as "current NAV per share" — it matches what an LP would actually receive on a `withdrawCollateral` call *after* a keeper has flushed the expired queue head (which is itself a precondition for any LP operation, per the **E415** gate). Internally it delegates to `VirtualShareLib.computeVirtualSharePrice` via the library's direct-storage access (markets, buckets, swapPositions, expiry queue, rate index).

The result is floored at `Utils.MIN_SHARE_PRICE`, same as `getPoolSharePrice`. Safe to call off-chain; does not mutate state.

***

## 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 `Views.getCalculatedAvailableLiquidity(marketId)` , which lives on the read-only `Views` contract, to convert "unlocked collateral" into "maximum notional a new swap can use" (it combines this value with the live base rate, swap term, and leverage multiplier via `SwapFormulas.calculateAvailableLiquidity`).


---

# 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.
