> ## Documentation Index
> Fetch the complete documentation index at: https://docs.windrose.market/llms.txt
> Use this file to discover all available pages before exploring further.

# LiquidityLocker

> Holds every graduated Uniswap v4 position forever. The only thing it can do is collect swap fees.

When a curve graduates, the full-range Uniswap v4 position it mints is owned by `LiquidityLocker`, and the locker has no code path that decreases liquidity, burns or transfers a position. Liquidity seeded at graduation is therefore locked for as long as the chain runs; the swap fees the position earns can be collected by anyone to the protocol's fee collector.

Source: `contracts/src/launch/LiquidityLocker.sol` (`IERC721Receiver`, `Ownable2Step`, `ReentrancyGuard`). One locker per chain, set on the factory (`LaunchFactory.locker()`); address in [Addresses](/protocol/addresses).

## Interface

```solidity theme={"system"}
IPositionManager public immutable posm;
address public feeCollector;

function setFeeCollector(address c) external onlyOwner;       // ZeroAddress for 0x0
function collect(uint256 tokenId) external nonReentrant;       // permissionless
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4);
```

**`collect(tokenId)`** reads the position's pool key from the PositionManager and submits two actions in one `modifyLiquidities` call: `DECREASE_LIQUIDITY` with `liquidity = 0` and both minimum amounts `0`, followed by `TAKE_PAIR` of `currency0` and `currency1` to `feeCollector`. A zero-liquidity decrease is how Uniswap v4 realises accrued fees without touching the principal; `TAKE_PAIR` then pays them out. Anyone can call it for any position the locker owns; the deadline is the current block timestamp. Emits `FeesCollected(tokenId, feeCollector)`.

**`setFeeCollector`** changes where future collections are paid (`FeeCollectorSet(feeCollector)`). The deploy script sets it to `FEE_COLLECTOR`, the deployer by default.

**`onERC721Received`** returns the selector so the PositionManager can mint positions to the locker.

## What it cannot do

* Decrease liquidity by any non-zero amount: the amount is a hard-coded `0` in `collect`, and no other function calls `modifyLiquidities`.
* Transfer or burn a position: there is no `transferFrom`, `safeTransferFrom` or `burn` call anywhere in the contract, and it holds no approvals.
* Be upgraded: it is a plain contract with an immutable `posm`.

The owner's only powers are `setFeeCollector` and the `Ownable2Step` transfer. The position principal is out of everyone's reach, including the owner and the creator of the launch.

## Fee destination

Fees arrive in both pool currencies: the launch token and its quote asset (the dollar, EURC or a synthetic such as wINR). They go to `feeCollector`, not to the launch creator; creators earn their share of the curve's trading fee only (see [BondingCurve](/protocol/contracts/bonding-curve) and [Fees](/concepts/fees)).

## Reading a locked position

```ts theme={"system"}
import { bondingCurveAbi, stateViewAbi } from "@launchpad/abis";

const poolId = await client.readContract({ address: curve, abi: bondingCurveAbi, functionName: "poolId" });
const positionTokenId = await client.readContract({ address: curve, abi: bondingCurveAbi, functionName: "positionTokenId" });
const [sqrtPriceX96, tick] = await client.readContract({ address: stateView, abi: stateViewAbi, functionName: "getSlot0", args: [poolId] });
const liquidity = await client.readContract({ address: stateView, abi: stateViewAbi, functionName: "getLiquidity", args: [poolId] });
```

Converting `sqrtPriceX96` into a quote-per-token price is shown in [Launch lifecycle](/protocol/integrate/launch-lifecycle). The PositionManager's `ownerOf(positionTokenId)` returns the locker.

`contracts/test/BondingCurve.t.sol` (`test_graduate_seedsLockedPoolAtCurvePrice` and the EUR and six-decimal variants) asserts that the position is owned by the locker after graduation.
