PositionManager is used for position management, including creating positions, modifying liquidity, deleting positions, and other operations.
The PositionManager contract mainly includes the following interfaces:
- initializePool: Initialize the pool
- modifyLiquidities: Modify liquidity
- modifyLiquiditiesWithoutUnlock: Modify liquidity (without unlocking)
According to the Uniswap v4 workflow diagram, both modifyLiquidities
and modifyLiquiditiesWithoutUnlock
execute the _handleAction method through BaseActionsRouter._executeActionsWithoutUnlock, which executes different operations specified by the user.
PositionManager mainly includes the following two types of operations:
-
Liquidity modification operations
- INCREASE_LIQUIDITY: Increase liquidity
- INCREASE_LIQUIDITY_FROM_DELTAS: Increase liquidity using flash accounting balance
- DECREASE_LIQUIDITY: Decrease liquidity
- MINT_POSITION: Create a position
- MINT_POSITION_FROM_DELTAS: Create a position using flash accounting balance
- BURN_POSITION: Burn a position
-
Balance settlement operations
- SETTLE_PAIR: Settle the debt of a token pair, the caller pays tokens to the
PoolManager
contract - TAKE_PAIR: Withdraw the balance of a token pair
- SETTLE: Settle the debt of a single token, pay tokens to the
PoolManager
contract - TAKE: Withdraw the balance of a single token
- CLOSE_CURRENCY: Settle or withdraw the balance of a single token
- CLEAR_OR_TAKE: Abandon or withdraw the balance of a single token
- SWEEP: Withdraw the balance of a single token from the PositionManager
- SETTLE_PAIR: Settle the debt of a token pair, the caller pays tokens to the
Initialize the pool.
/// @inheritdoc IPoolInitializer_v4
function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24) {
try poolManager.initialize(key, sqrtPriceX96) returns (int24 tick) {
return tick;
} catch {
return type(int24).max;
}
}
Call the PoolManager.initialize method to initialize the pool.
The PositionManager contract adopts the design philosophy of a command line. It does not provide interfaces for each operation separately but allows users to combine different operation commands through modifyLiquidities and execute them sequentially.
This method is the standard entry point for PositionManager.
/// @inheritdoc IPositionManager
function modifyLiquidities(bytes calldata unlockData, uint256 deadline)
external
payable
isNotLocked
checkDeadline(deadline)
{
_executeActions(unlockData);
}
PositionManager
inherits BaseActionsRouter
, and modifyLiquidities
only checks the deadline
and calls the _executeActions method.
The modifyLiquiditiesWithoutUnlock
method is similar to the modifyLiquidities
method but does not perform the unlock operation.
External contracts need to ensure that the PoolManager.unlock method has been called to unlock.
/// @inheritdoc IPositionManager
function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params)
external
payable
isNotLocked
{
_executeActionsWithoutUnlock(actions, params);
}
All contracts inheriting BaseActionsRouter need to implement the _handleAction
method to execute specific operations based on the Action and parameters passed by the user.
function _handleAction(uint256 action, bytes calldata params) internal virtual override {
if (action < Actions.SETTLE) {
if (action == Actions.INCREASE_LIQUIDITY) {
(uint256 tokenId, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_increase(tokenId, liquidity, amount0Max, amount1Max, hookData);
return;
} else if (action == Actions.INCREASE_LIQUIDITY_FROM_DELTAS) {
(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) =
params.decodeIncreaseLiquidityFromDeltasParams();
_increaseFromDeltas(tokenId, amount0Max, amount1Max, hookData);
return;
} else if (action == Actions.DECREASE_LIQUIDITY) {
(uint256 tokenId, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_decrease(tokenId, liquidity, amount0Min, amount1Min, hookData);
return;
} else if (action == Actions.MINT_POSITION) {
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintParams();
_mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
} else if (action == Actions.MINT_POSITION_FROM_DELTAS) {
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintFromDeltasParams();
_mintFromDeltas(poolKey, tickLower, tickUpper, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
} else if (action == Actions.BURN_POSITION) {
// Will automatically decrease liquidity to 0 if the position is not already empty.
(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeBurnParams();
_burn(tokenId, amount0Min, amount1Min, hookData);
return;
}
} else {
if (action == Actions.SETTLE_PAIR) {
(Currency currency0, Currency currency1) = params.decodeCurrencyPair();
_settlePair(currency0, currency1);
return;
} else if (action == Actions.TAKE_PAIR) {
(Currency currency0, Currency currency1, address recipient) = params.decodeCurrencyPairAndAddress();
_takePair(currency0, currency1, _mapRecipient(recipient));
return;
} else if (action == Actions.SETTLE) {
(Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool();
_settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency));
return;
} else if (action == Actions.TAKE) {
(Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256();
_take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency));
return;
} else if (action == Actions.CLOSE_CURRENCY) {
Currency currency = params.decodeCurrency();
_close(currency);
return;
} else if (action == Actions.CLEAR_OR_TAKE) {
(Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256();
_clearOrTake(currency, amountMax);
return;
} else if (action == Actions.SWEEP) {
(Currency currency, address to) = params.decodeCurrencyAndAddress();
_sweep(currency, _mapRecipient(to));
return;
} else if (action == Actions.WRAP) {
uint256 amount = params.decodeUint256();
_wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9))));
return;
} else if (action == Actions.UNWRAP) {
uint256 amount = params.decodeUint256();
_unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO));
return;
}
}
revert UnsupportedAction(action);
}
ActionsLibrary defines all actions
in numerical order:
- Operations less than
Actions.SETTLE
are liquidity modification operations - Operations greater than or equal to
Actions.SETTLE
are balance settlement (accounting) operations
The logic for each Action is introduced below:
Increase liquidity.
(uint256 tokenId, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_increase(tokenId, liquidity, amount0Max, amount1Max, hookData);
return;
Decode the parameters and call the _increase method.
Increase liquidity using flash accounting balance.
(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) =
params.decodeIncreaseLiquidityFromDeltasParams();
_increaseFromDeltas(tokenId, amount0Max, amount1Max, hookData);
return;
Decode the parameters and call the _increaseFromDeltas method.
Decrease liquidity.
(uint256 tokenId, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_decrease(tokenId, liquidity, amount0Min, amount1Min, hookData);
return;
Decode the parameters and call the _decrease method.
Create a position.
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintParams();
_mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
Decode the parameters and call the _mint method.
Create a position using flash accounting balance.
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintFromDeltasParams();
_mintFromDeltas(poolKey, tickLower, tickUpper, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
Decode the parameters and call the _mintFromDeltas method.
Here, the _mapRecipient method is used to calculate the owner
address.
Burn a position.
// Will automatically decrease liquidity to 0 if the position is not already empty.
(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeBurnParams();
_burn(tokenId, amount0Min, amount1Min, hookData);
return;
Decode the parameters and call the _burn method.
Settle the debt of a token pair, the caller pays tokens to the PoolManager
contract.
(Currency currency0, Currency currency1) = params.decodeCurrencyPair();
_settlePair(currency0, currency1);
return;
Decode the parameters and call the _settlePair method.
Withdraw the balance of a token pair, the PoolManager
pays tokens to the recipient
.
(Currency currency0, Currency currency1, address recipient) = params.decodeCurrencyPairAndAddress();
_takePair(currency0, currency1, _mapRecipient(recipient));
return;
Decode the parameters and call the _takePair method.
Use the _mapRecipient method to calculate the recipient
address.
Settle the debt of a single token, the caller pays tokens to the PoolManager
contract.
(Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool();
_settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency));
return;
Decode the parameters, use the _mapPayer method to determine the payer, use the _mapSettleAmount method to calculate the amount of tokens to be settled, and finally call the _settle method.
Withdraw the balance of a single token, the PoolManager
pays tokens to the recipient
.
(Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256();
_take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency));
return;
Decode the parameters and call the _take method.
Use the _mapRecipient method to calculate the recipient
address.
Use the _mapTakeAmount method to calculate the amount of tokens to be withdrawn.
Settle or withdraw the balance of a single token.
When it is uncertain whether to settle or withdraw, you can use CLOSE_CURRENCY
to close the position of the specified token.
Currency currency = params.decodeCurrency();
_close(currency);
return;
Decode the parameters and call the _close method.
Abandon or withdraw the balance of a single token.
When the balance is less than or equal to amountMax
, abandon the token; otherwise, withdraw the token.
(Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256();
_clearOrTake(currency, amountMax);
return;
Decode the parameters and call the _clearOrTake method.
Withdraw the balance of a single token from the PositionManager
, the PositionManager
pays tokens to the recipient
.
(Currency currency, address to) = params.decodeCurrencyAndAddress();
_sweep(currency, _mapRecipient(to));
return;
Decode the parameters and call the _sweep method.
Use the _mapRecipient method to calculate the to
address.
Wrap tokens, converting the native ETH
of the PositionManager
contract into WETH
.
uint256 amount = params.decodeUint256();
_wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9))));
return;
Decode the parameters and call the _wrap method.
Use the _mapWrapUnwrapAmount method to calculate the amount of tokens to be wrapped.
Unwrap tokens, converting the WETH
of the PositionManager
contract into native ETH
.
uint256 amount = params.decodeUint256();
_unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO));
return;
Decode the parameters and call the _unwrap method.
Use the _mapWrapUnwrapAmount method to calculate the amount of tokens to be unwrapped.
Increase the liquidity of a position.
Input parameters:
tokenId
: Position ID, i.e., the ERC721 token ID assigned by PositionManagerliquidity
: The amount of liquidity to be increasedamount0Max
: The maximum amount oftoken0
to be providedamount1Max
: The maximum amount oftoken1
to be providedhookData
: Hook data, used to pass tobeforeModifyLiquidity
andafterModifyLiquidity
Hooks functions
/// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position
function _increase(
uint256 tokenId,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
) internal onlyIfApproved(msgSender(), tokenId) {
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max);
}
Get the pool information and position information through getPoolAndPositionInfo
.
Call the _modifyLiquidity method to complete the liquidity modification. Return liquidityDelta
and feesAccrued
. Among them:
liquidityDelta
is the liquidity change value that the caller needs to settle, all non-positive;feesAccrued
is the fees that the position can receive up to the current point, all non-negative.
Since liquidityDelta
already includes feeAccrued
, and amount0Max
and amount1Max
do not include fees (only calculate the liquidity itself), liquidityDelta - feesAccrued
needs to be used as the input parameter for the maximum value check.
Note:
liquidityDelta
is the final amount of tokensamount0
andamount1
that the caller needs to pay, represented by negative numbers.
This method is similar to the _increase method. The difference is that _increaseFromDeltas
does not need to specify the specific amount of liquidity but calculates the amount of liquidity to be increased by querying the current flash accounting balance of the PositionManager
contract in the PoolManager
for the two tokens of the pool.
Finally, call the _modifyLiquidity method to complete the liquidity modification.
/// @dev The liquidity delta is derived from open deltas in the pool manager.
function _increaseFromDeltas(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData)
internal
onlyIfApproved(msgSender(), tokenId)
{
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
uint256 liquidity;
{
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
// Use the credit on the pool manager as the amounts for the mint.
liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(info.tickLower()),
TickMath.getSqrtPriceAtTick(info.tickUpper()),
_getFullCredit(poolKey.currency0),
_getFullCredit(poolKey.currency1)
);
}
// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max);
}
The _getFullCredit method ensures that the flash accounting balance of the PositionManager
in the PoolManager
is always non-negative, meaning that there are tokens that can be withdrawn; otherwise, the transaction will be rolled back.
Decrease the liquidity of a position.
Input parameters:
tokenId
: Position ID, i.e., the ERC721 token ID assigned by PositionManagerliquidity
: The amount of liquidity to be decreasedamount0Min
: The minimum amount oftoken0
to be receivedamount1Min
: The minimum amount oftoken1
to be receivedhookData
: Hook data, used to pass tobeforeModifyLiquidity
andafterModifyLiquidity
Hooks functions
/// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position
function _decrease(
uint256 tokenId,
uint256 liquidity,
uint128 amount0Min,
uint128 amount1Min,
bytes calldata hookData
) internal onlyIfApproved(msgSender(), tokenId) {
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
// Note: the tokenId is used as the salt.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, -(liquidity.toInt256()), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min);
}
Get the pool information and position information through getPoolAndPositionInfo
.
Call the _modifyLiquidity method to complete the liquidity modification. Return liquidityDelta
and feesAccrued
. Among them:
liquidityDelta
is the liquidity change value that the caller needs to withdraw, all non-negative;feesAccrued
is the fees that the position can receive up to the current point, all non-negative.
Since liquidityDelta
already includes feeAccrued
, and amount0Min
and amount1Min
do not include fees (only calculate the liquidity itself), liquidityDelta - feesAccrued
needs to be used as the input parameter for the minimum value check.
Note:
liquidityDelta
is the final amount of tokens that the caller can withdraw, represented by positive numbers or 0.
Since Uniswap v4 does not provide a direct method to withdraw fees, you can use the DECRESAE_LIQUIDITY
operation to withdraw fees by setting liquidity
, amount0Min
, and amount1Min
to 0.
Create a position.
Input parameters:
poolKey
: The key of the pooltickLower
: The lower tick of the positiontickUpper
: The upper tick of the positionliquidity
: The amount of liquidity to be createdamount0Max
: The maximum amount oftoken0
to be providedamount1Max
: The maximum amount oftoken1
to be providedowner
: The owner of the positionhookData
: Hook data, used to pass tobeforeModifyLiquidity
andafterModifyLiquidity
Hooks functions
function _mint(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) internal {
// mint receipt token
uint256 tokenId;
// tokenId is assigned to current nextTokenId before incrementing it
unchecked {
tokenId = nextTokenId++;
}
_mint(owner, tokenId);
// Initialize the position info
PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper);
positionInfo[tokenId] = info;
// Store the poolKey if it is not already stored.
// On UniswapV4, the minimum tick spacing is 1, which means that if the tick spacing is 0, the pool key has not been set.
bytes25 poolId = info.poolId();
if (poolKeys[poolId].tickSpacing == 0) {
poolKeys[poolId] = poolKey;
}
// fee delta can be ignored as this is a new position
(BalanceDelta liquidityDelta,) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
liquidityDelta.validateMaxIn(amount0Max, amount1Max);
}
Assign the ERC721 token ID tokenId
, call the _mint
method to mint the ERC721 token for the owner
, representing the position.
Initialize the position information. positionInfo
represents the mapping of tokenId
to position information.
If the poolKey
is not stored, store the poolKey
.
Therefore, the pool information poolKey
and position information positionInfo
can be returned based on the tokenId
later.
Call the _modifyLiquidity method to complete the liquidity modification. Return liquidityDelta
and feesAccrued
. Since freeAccrued
is 0 at this time, it can be ignored. Check whether liquidityDelta
exceeds amount0Max
and amount1Max
.
Create a position using flash accounting balance.
Input parameters:
poolKey
: The key of the pooltickLower
: The lower tick of the positiontickUpper
: The upper tick of the positionamount0Max
: The maximum amount oftoken0
to be providedamount1Max
: The maximum amount oftoken1
to be providedowner
: The owner of the positionhookData
: Hook data, used to pass tobeforeModifyLiquidity
andafterModifyLiquidity
Hooks functions
Similar to the _mint
method, the difference is that _mintFromDeltas
does not need to specify the specific amount of liquidity but calculates the amount of liquidity to be increased by querying the current flash accounting balance of the PositionManager
contract in the PoolManager
for the two tokens of the pool.
Finally, call the _mint method to create the position.
function _mintFromDeltas(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) internal {
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
// Use the credit on the pool manager as the amounts for the mint.
uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
_getFullCredit(poolKey.currency0),
_getFullCredit(poolKey.currency1)
);
_mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData);
}
Burn a position.
Input parameters:
tokenId
: Position ID, i.e., the ERC721 token ID assigned by PositionManageramount0Min
: The minimum amount oftoken0
to be receivedamount1Min
: The minimum amount oftoken1
to be receivedhookData
: Hook data, used to pass tobeforeModifyLiquidity
andafterModifyLiquidity
Hooks functions
/// @dev this is overloaded with ERC721Permit_v4._burn
function _burn(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData)
internal
onlyIfApproved(msgSender(), tokenId)
{
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
uint256 liquidity = uint256(_getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper()));
address owner = ownerOf(tokenId);
// Clear the position info.
positionInfo[tokenId] = PositionInfoLibrary.EMPTY_POSITION_INFO;
// Burn the token.
_burn(tokenId);
// Can only call modify if there is non zero liquidity.
BalanceDelta feesAccrued;
if (liquidity > 0) {
BalanceDelta liquidityDelta;
// do not use _modifyLiquidity as we do not need to notify on modification for burns.
IPoolManager.ModifyLiquidityParams memory params = IPoolManager.ModifyLiquidityParams({
tickLower: info.tickLower(),
tickUpper: info.tickUpper(),
liquidityDelta: -(liquidity.toInt256()),
salt: bytes32(tokenId)
});
(liquidityDelta, feesAccrued) = poolManager.modifyLiquidity(poolKey, params, hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min);
}
// deletes then notifies the subscriber
if (info.hasSubscriber()) _removeSubscriberAndNotifyBurn(tokenId, owner, info, liquidity, feesAccrued);
}
Get the pool information and position information through getPoolAndPositionInfo
.
Query the current liquidity of the position through the PoolManager
.
Clear the position information and burn the ERC721 token.
If the liquidity is greater than 0, call the poolManager.modifyLiquidity method to remove all liquidity of the position. Return liquidityDelta
and feesAccrued
.
Since liquidityDelta
already includes feeAccrued
, and amount0Min
and amount1Min
do not include fees (only calculate the liquidity itself), liquidityDelta - feesAccrued
needs to be used as the input parameter for the minimum value check.
If the position has a subscriber, call the _removeSubscriberAndNotifyBurn
method to notify the subscriber.
Note that after the position is burned, the caller does not receive any tokens. These tokens are recorded in the flash accounting balance of the PoolManager
; the caller needs to combine operations such as TAKE_PAIR to withdraw the tokens to their account.
Settle the debt of a token pair. The caller needs to pay tokens to the PoolManager
.
Input parameters:
currency0
: Token 0currency1
: Token 1
function _settlePair(Currency currency0, Currency currency1) internal {
// the locker is the payer when settling
address caller = msgSender();
_settle(currency0, caller, _getFullDebt(currency0));
_settle(currency1, caller, _getFullDebt(currency1));
}
Call the _settle method to settle the balances of token 0 and token 1. At this time, the flash accounting balance in the PoolManager
must be non-positive, meaning that the caller needs to pay tokens.
Use the _getFullDebt method to get the debt of the PositionManager
in the PoolManager
.
Withdraw the balance of a token pair, the PoolManager
pays tokens to the recipient
.
function _takePair(Currency currency0, Currency currency1, address recipient) internal {
_take(currency0, recipient, _getFullCredit(currency0));
_take(currency1, recipient, _getFullCredit(currency1));
}
Call the _take method to withdraw the balances of token 0 and token 1. According to the _getFullCredit method, the flash accounting balance of the PoolManager
must be non-negative, meaning that the caller is allowed to withdraw tokens.
Settle or withdraw the balance of a single token. Depending on the sign of currencyDelta
, decide whether to settle the debt or withdraw the balance.
function _close(Currency currency) internal {
// this address has applied all deltas on behalf of the user/owner
// it is safe to close this entire delta because of slippage checks throughout the batched calls.
int256 currencyDelta = poolManager.currencyDelta(address(this), currency);
// the locker is the payer or receiver
address caller = msgSender();
if (currencyDelta < 0) {
// Casting is safe due to limits on the total supply of a pool
_settle(currency, caller, uint256(-currencyDelta));
} else {
_take(currency, caller, uint256(currencyDelta));
}
}
Call the poolManager.currencyDelta method to get the flash accounting balance of the PositionManager
in the PoolManager
.
If currencyDelta
is less than 0, it means that the PositionManager
owes tokens, call the _settle method to pay tokens to the PoolManager
, and the payer
is the caller.
Otherwise, call the _take method to withdraw tokens from the PoolManager
, and the token recipient is the caller.
Abandon or withdraw the balance of a single token.
Input parameters:
currency
: TokenamountMax
: Maximum amount. If less than or equal to this value, abandon the token; otherwise, withdraw the token.
/// @dev integrators may elect to forfeit positive deltas with clear
/// if the forfeit amount exceeds the user-specified max, the amount is taken instead
/// if there is no credit, no call is made.
function _clearOrTake(Currency currency, uint256 amountMax) internal {
uint256 delta = _getFullCredit(currency);
if (delta == 0) return;
// forfeit the delta if its less than or equal to the user-specified limit
if (delta <= amountMax) {
poolManager.clear(currency, delta);
} else {
_take(currency, msgSender(), delta);
}
}
Use the _getFullCredit method to get the flash accounting balance of the PositionManager
in the PoolManager
. This value must be non-negative.
If delta
is 0, return directly.
If delta
is less than or equal to amountMax
, call the poolManager.clear method to abandon the token.
Otherwise, call the _take method to withdraw the token from the PoolManager
to the caller.
Withdraw the balance of a single token from the PositionManager
(note, not the PoolManager
).
/// @notice Sweeps the entire contract balance of specified currency to the recipient
function _sweep(Currency currency, address to) internal {
uint256 balance = currency.balanceOfSelf();
if (balance > 0) currency.transfer(to, balance);
}
Call the currency.balanceOfSelf
method to get the balance of the specified token in the PositionManager
contract.
If the balance is greater than 0, call the currency.transfer
method to transfer the token to the to
address.
/// @dev if there is a subscriber attached to the position, this function will notify the subscriber
function _modifyLiquidity(
PositionInfo info,
PoolKey memory poolKey,
int256 liquidityChange,
bytes32 salt,
bytes calldata hookData
) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) {
(liquidityDelta, feesAccrued) = poolManager.modifyLiquidity(
poolKey,
IPoolManager.ModifyLiquidityParams({
tickLower: info.tickLower(),
tickUpper: info.tickUpper(),
liquidityDelta: liquidityChange,
salt: salt
}),
hookData
);
if (info.hasSubscriber()) {
_notifyModifyLiquidity(uint256(salt), liquidityChange, feesAccrued);
}
}
Call the poolManager.modifyLiquidity method to complete the liquidity modification. Return liquidityDelta
and feesAccrued
.
If the position has a subscriber, call the _notifyModifyLiquidity
method to notify the subscriber.
The payer
pays tokens to the poolManager
. Implement the _pay method of DeltaResolver
.
// implementation of abstract function DeltaResolver._pay
function _pay(Currency currency, address payer, uint256 amount) internal override {
if (payer == address(this)) {
currency.transfer(address(poolManager), amount);
} else {
// Casting from uint256 to uint160 is safe due to limits on the total supply of a pool
permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency));
}
}
If the payer
is the PositionManager
contract address, directly call the currency.transfer
method to transfer the token to the poolManager
.
Otherwise, call the permit2.transferFrom
method to transfer the token from the payer
account to the poolManager
.
Note: The
payer
needs to call thepermit
method in advance to authorize thePositionManager
to transfer tokens from thepayer
account.
Wrap tokens, converting the native ETH
of the PositionManager
contract into WETH
.
/// @dev The amount should already be <= the current balance in this contract.
function _wrap(uint256 amount) internal {
if (amount > 0) WETH9.deposit{value: amount}();
}
Call the WETH9.deposit
method to convert amount
of ETH
into WETH
.
Unwrap tokens, converting the WETH
of the PositionManager
contract into native ETH
.
/// @dev The amount should already be <= the current balance in this contract.
function _unwrap(uint256 amount) internal {
if (amount > 0) WETH9.withdraw(amount);
}
Call the WETH9.withdraw
method to convert amount
of WETH
into ETH
.