Skip to main content

Mint Position

Similar to Uniswap v3, liquidity positions are minted as ERC-721 tokens and depend on a periphery contract. v4's PositionManager contract will facilitate liquidity management

Context

Please note that PositionManager is a command-based contract, where integrators will be encoding commands and their corresponding parameters.

Setup

See the setup guide

Guide

Below is a step-by-step guide for minting a v4 liquidity position, in solidity

1. Import and define IPositionManager

import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";

// inside a contract, test, or foundry script:
IPositionManager posm = IPositionManager(<address>);

2. Encode Actions

To mint a position, two actions are required:

  • mint operation - the creation of the liquidity position
  • settle pair - the two tokens to be paid by msg.sender

If providing ETH liquidity, a third action is required:

  • sweep - to recover excess eth sent to the position manager
import {Actions} from "v4-periphery/src/libraries/Actions.sol";

bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));

// For ETH liquidity positions
bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR), uint8(Actions.SWEEP));

3. Encode Parameters

bytes[] memory params = new bytes[](2); // new bytes[](3) for ETH liquidity positions

The MINT_POSITION action requires the following parameters:

ParameterTypeDescription
poolKeyPoolKeywhere the liquidity will be added to
tickLowerint24the lower tick boundary of the position
tickUpperint24the upper tick boundary of the position
liquidityuint256the amount of liquidity units to mint
amount0Maxuint128the maximum amount of currency0 msg.sender is willing to pay
amount1Maxuint128the maximum amount of currency1 msg.sender is willing to pay
recipientaddressthe address that will receive the liquidity position (ERC-721)
hookDatabytesarbitrary data that will be forwarded to hook functions
Currency currency0 = Currency.wrap(<tokenAddress1>); // tokenAddress1 = 0 for native ETH
Currency currency1 = Currency.wrap(<tokenAddress2>);
PoolKey poolKey = PoolKey(currency0, currency1, 3000, 60, IHooks(hook));

params[0] = abi.encode(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);

The SETTLE_PAIR action requires the following parameters:

  • currency0 - Currency, one of the tokens to be paid by msg.sender
  • currency1 - Currency, the other token to be paid by msg.sender
params[1] = abi.encode(currency0, currency1);

The SWEEP action requires the following parameters:

  • currency - Currency, token to sweep - most commonly native Ether: CurrencyLibrary.ADDRESS_ZERO
  • recipient - address, where to send excess tokens
params[2] = abi.encode(currency, recipient);

4. Submit Call

The entrypoint for all liquidity operations is modifyLiquidities()

uint256 deadline = block.timestamp + 60;

uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0;

posm.modifyLiquidities{value: valueToPass}(
abi.encode(actions, params),
deadline
);

Additional notes:

  • To obtain balance changes, callers should read token balances before and after the .modifyLiquidities() call