Manage liquidity for CL and Bin pool
We'll utilize the periphery contracts from v4-periphery to manage liqudity in v4.
The topic would include:
- Permit2 integration
- PositionManager execution flow
- Type of cl pool liquidity actions (
CLPositionManager
) - Type of bin pool liquidity actions (
BinPositionManager
) - Type of settlement actions (applicable for both pool type)
1. Permit2 integration
Both CLPositionManager and BinPositionManager integrates with Permit2
FrontendFor the frontend, this means that any operation involving token transfers from the user requires the user to sign a permit message and include that signature in the multiCall.
Example:
bytes[] memory calls = new bytes[](2);
// signature from user
calls[0] = abi.encodeWithSelector(IAllowanceTransfer.Permit.selector, owner, PermitSingle permitSIngle, bytes signature);
// modifyLiquidites call - explained in next section
calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, abi.encode(...));
positonManager.multiCall(...)
For solidity contract calls to PositionManager, make sure to first call permit2.approve
to authorize the position manager.
// call permit2.approve(address token, address spender, uint160 amount, uint48 expiration);
// example infinite approval to clPositionManager
permit2.approve(tokenAddress, address(clPositionManager), type(uint160).max, type(uint48).max));
2. PositionManager execution flow
If you are familar with Universal Router, PositionManager follows the same approach (one method, with actions and params).
// @notice Unlocks Vault and batches actions for modifying liquidity
function modifyLiquidities(bytes calldata payload, uint256 deadline) external payable;
// @notice Batches actions for modifying liquidity without getting a lock from vault
function modifyLiquiditiesWithoutLock(bytes calldata actions, bytes[] calldata params) external payable;
The bytes calldata payload
in modifyLiquidities
is abi.encode(actions, params)
of the input in modifyLiquiditiesWithoutLock(bytes calldata actions, bytes[] calldata params)
-
List of
actions
can be found in Actions.sol. Actions includes liquidity relatedCL_INCREASE_LIQUIDITY
or settlement related such asSETTLE_PAIR
. -
bytes[] params
is an array of bytes string. Each element in the array is the abi-encoded input for each respective action.
Given actions = 0x021717
, it would mean 3 actions processed in order:
0x02
:CL_MINT_POSITION
0x17
:CLOSE_CURRENCY
0x17
:CLOSE_CURRENCY
With 3 actions, it would mean bytes[] params
of length = 3.
params[0]
= abi encoded param ofCL_MINT_POSITION
actionparams[1]
= abi encoded param ofCLOSE_CURRENCY
actionparams[2]
= abi encoded param ofCLOSE_CURRENCY
action
Example
This example tries to mint a position with CLPositionManager and uses Planner which is a helper class to build actions.
PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper});
// Assume other field such as liquidity and amount0Max are present
Plan memory planner = Planner.init();
// For each action to add, call planner.add
planner.add(ACTIONS.CL_MINT_POSITION, abi.encode(config, liquidity, amount0Max, amount1Max, recipient, hookData));
// finalizeModifyLiquidityWithClose is a helper method to add CLOSE_CURRENCY actions for both currencies
bytes memory payload = planner.finalizeModifyLiquidityWithClose(config.poolKey)
uint256 deadline = block.timestamp;
clPositionManager.modifyLiquidities(payload, deadline);
3. Type of CL pool liquidity actions (CLPositionManager
)
Below list down CL pool related actions and its associated params.
Before we proceed, PositionConfig
is a commonly used struct in CL liquidity related so we'll document here once
struct PositionConfig {
PoolKey poolKey; // struct which determine the poolKey
int24 tickLower; // tickLower for the position
int24 tickUpper; // tickUpper for the position
}
CL_MINT_POSITION
Mint a new position
{
PositionConfig config; // config for the position
uint256 liquidity; // the amount of liquidity to increase
uint128 amount0Max; // max amount0, revert if amount0 exceeds amount0Max
uint128 amount1Max; // max amount1, revert if amount1 exceeds amount1Max
address owner; // owner to recieve the minted NFT
bytes hookData: // hook data to pass to hook, or empty bytes if nothing to pass
}
CL_BURN_POSITION
Burn the NFT and automatically decrease liquidity to 0 if the position is not already empty.
{
uint256 tokenId; // the NFT tokenId to burn
PositionConfig config; // config for the position
uint128 amount0Min; // min amount0, revert if less than amount0Min is removed
uint128 amount1Min: min amount1, revert if less than amount1Min is removed
bytes hookData: // hook data to pass to hook, or empty bytes if nothing to pass
}
CL_INCREASE_LIQUIDITY
Increase liquidity from existing position
{
uint256 tokenId; // the NFT tokenId to increase liquidity
PositionConfig config; // config for the position
uint256 liquidity; // the amount of liquidity to increase
uint128 amount0Max; // max amount0, revert if more than amount0Max is added
uint128 amount1Max: max amount1, revert if more than amount1Max is added
bytes hookData: // hook data to pass to hook, or empty bytes if nothing to pass
}
CL_DECREASE_LIQUIDITY
Decrease liquidity from existing position
{
uint256 tokenId; // the NFT tokenId to decrease liquidity
PositionConfig config; // config for the position
uint256 liquidity; // the amount of liquidity to decrease
uint128 amount0Min; // min amount0, revert if less than amount0Min is removed
uint128 amount1Min: min amount1, revert if less than amount1Min is removed
bytes hookData: // hook data to pass to hook, or empty bytes if nothing to pass
}
4. Type of Bin pool liquidity actions (BinPositionManager
)
Add liqudiity, will mint the correspond ERC-1155 token.
BIN_ADD_LIQUIDITY
IBinPositionManager.BinAddLiquidityParams {
PoolKey poolKey; // struct which identify the pool
uint128 amount0; // amount of token0
uint128 amount1; // amount of token1
uint128 amount0Min; // min amount of token0, revert if required token is lesser
uint128 amount1Min; // min amount of token1, revert if required token is lesser
uint256 activeIdDesired; // active id preferred when adding liquidity
uint256 idSlippage; // max slippage on active id, revert if activeId changes more than idSlippage value
int256[] deltaIds; // list of delta ids to add liquidity (deltaId = activeId - desiredId). see helper method convertToRelative above
uint256[] distributionX; // distribution of token0 with sum(distributionX) = 100e18 (100%)
uint256[] distributionY; // distribution of token1 with sum(distributionY) = 100e18 (100%)
address to; // address of recipient to receive the 1155 token which represent ownership of liquidity
}
BIN_REMOVE_LIQUIDITY
Remove liquidity, will burn the corresponding ERC-1155 token.
IBinPositionManager.BinRemoveLiquidityParams {
PoolKey poolKey; // struct which identify the pool
uint128 amount0Min; // min amount of token0 to receive, revert if required token is lesser
uint128 amount1Min; // min amount of token1 to receive, revert if required token is lesser
uint256[] ids; // list of bin ids
uint256[] amounts; // list of lquidity to remove for each bin
address from; // Address of holder who owns the 1155 token
}
5. Type of settlement actions
These action are applicable for both CLPositionManager and BinPositionManager.
SETTLE_PAIR
Pay and settle currency pair. User's token0 and token1 will be transferred from user and paid. This is commonly used for increase liquidity or mint action.
{
Currency currency0; // address of currency0
Currency currency1; // address of currency1
}
TAKE_PAIR
Take all amount owed from currency pair. Owed token0 and token1 will be transfered to to
. This is commonly used for decrease liquidity or burn action.
{
Currency currency0; // address of currency0
Currency currency1; // address of currency1
address to; // recipient of the tokens
}
SETTLE
Pay and settle currency.
For amount, there are constants ActionConstants.
amount=ActionConstants.CONTRACT_BALANCE
: amount will be currency.balance(positionManager)amount=ActionConstants.OPEN_DELTA
: amount will be the outstanding amount
{
Currency currency; // address of currency to settle
uint256 amount; // amount to settle
bool payerIsUser; // if true, transfer token from msgSender(), otherwise from positionManager
}
TAKE
Take all amount owed from currency.
For amount, there are constants ActionConstants.
amount=ActionConstants.OPEN_DELTA
: amount will be the owed amount
{
Currency currency; // address of currency to take
address recipient; // recipient of the token taken
uint256 amount; // amount to take
}
CLOSE_CURRENCY
Close currency by either take or settle so debt is netted off. If take, token will be sent to caller, if settle, token will be transferred from caller.
{
Currency currency; // currency to close
}
CLEAR_OR_TAKE
Either take amount owed from currency or forfeit the amount.
Users might opt to forfeit as the gas cost might of erc20 transfer is higher than the amount taken.
{
Currency currency; // currency to clear or take
uint256 amountMax; // forfeit if the amount to take is lesser than this
}
6. Other actions
SWEEP
Transfer any outstanding token balance in positionManager to to
.
{
Currency currency; // currency to sweep
address to; // recipient
}