Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/transformers/V4Utils.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@uniswap/v4-periphery/lib/permit2/src/interfaces/IPermit2.sol";
import "@uniswap/v4-core/src/interfaces/IHooks.sol";
import "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import "@uniswap/v4-periphery/src/libraries/PositionInfoLibrary.sol";
import "@uniswap/v4-periphery/src/libraries/Actions.sol";
import "@uniswap/v4-core/src/types/PoolKey.sol";
import "@uniswap/v4-core/src/types/Currency.sol";
import {CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import "@uniswap/v4-core/src/libraries/TickMath.sol";
import "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import "@uniswap/v4-core/src/types/PoolId.sol";
import "../utils/Swapper.sol";
import "../interfaces/IVault.sol";
import "./Transformer.sol";
/// @title V4Utils v1.0
/// @notice Utility functions for Uniswap V4 positions
/// It does not hold any ERC20 or NFTs.
/// It can be simply redeployed when new / better functionality is implemented
contract V4Utils is Transformer, Swapper, IERC721Receiver {
using SafeCast for uint256;
/// @notice Permit2 contract
IPermit2 public immutable permit2;
// events
event CompoundFees(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
event ChangeRange(uint256 indexed tokenId, uint256 newTokenId);
event WithdrawAndCollectAndSwap(uint256 indexed tokenId, address token, uint256 amount);
event SwapAndMint(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
event SwapAndIncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Action which should be executed on provided NFT
enum WhatToDo {
CHANGE_RANGE,
WITHDRAW_AND_COLLECT_AND_SWAP,
COMPOUND_FEES
}
/// @notice Complete description of what should be executed on provided NFT - different fields are used depending on specified WhatToDo
struct Instructions {
// what action to perform on provided Uniswap v4 position
WhatToDo whatToDo;
// target token for swaps (address(0) for native ETH)
Currency targetToken;
// for removing liquidity slippage
uint256 amountRemoveMin0;
uint256 amountRemoveMin1;
// amountIn0 is used for swap and also as minAmount0 for decreased liquidity + collected fees
uint256 amountIn0;
// if token0 needs to be swapped to targetToken - set values
uint256 amountOut0Min;
bytes swapData0; // encoded data from 0x api call (address,bytes) - allowanceTarget,data
// amountIn1 is used for swap and also as minAmount1 for decreased liquidity + collected fees
uint256 amountIn1;
// if token1 needs to be swapped to targetToken - set values
uint256 amountOut1Min;
bytes swapData1; // encoded data from 0x api call (address,bytes) - allowanceTarget,data
// for creating new positions with CHANGE_RANGE
uint24 fee;
int24 tickSpacing;
int24 tickLower;
int24 tickUpper;
// remove liquidity amount for COMPOUND_FEES (in this case should be probably 0) / CHANGE_RANGE / WITHDRAW_AND_COLLECT_AND_SWAP
uint128 liquidity;
// for adding liquidity slippage
uint256 amountAddMin0;
uint256 amountAddMin1;
// for all uniswap deadlineable functions
uint256 deadline;
// left over tokens will be sent to this address
address recipient;
// recipient of newly minted nft (the incoming NFT will ALWAYS be returned to from)
address recipientNFT;
// data sent with returned token to IERC721Receiver (optional)
bytes returnData;
// data sent with minted token to IERC721Receiver (optional)
bytes swapAndMintReturnData;
// hook address for CHANGE_RANGE operations (optional)
address hook;
// hook data for all operations which decrease liquidity (optional)
bytes decreaseLiquidityHookData;
// hook data for all operations which mint / increase liquidity (optional)
bytes increaseLiquidityHookData;
}
/// @notice Params for swap() function
/// Renamed because of conflict with SwapParams in PoolOperation.sol
struct SwapParamsV4 {
Currency tokenIn;
Currency tokenOut;
uint256 amountIn;
uint256 minAmountOut;
address recipient; // recipient of tokenOut and leftover tokenIn (if any leftover)
bytes swapData;
}
/// @notice Params for swapAndMint() function
struct SwapAndMintParams {
Currency token0;
Currency token1;
uint24 fee;
int24 tickSpacing;
int24 tickLower;
int24 tickUpper;
// how much is provided of token0 and token1
uint256 amount0;
uint256 amount1;
address recipient; // recipient of leftover tokens
address recipientNFT; // recipient of nft
uint256 deadline;
// source token for swaps (maybe either address(0) for native ETH, token0, token1 or another token)
// if swapSourceToken is another token than token0 or token1 -> amountIn0 + amountIn1 of swapSourceToken are expected to be available
Currency swapSourceToken;
// if swapSourceToken needs to be swapped to token0 - set values
uint256 amountIn0;
uint256 amountOut0Min;
bytes swapData0;
// if swapSourceToken needs to be swapped to token1 - set values
uint256 amountIn1;
uint256 amountOut1Min;
bytes swapData1;
// min amount to be added after swap
uint256 amountAddMin0;
uint256 amountAddMin1;
// data to be sent along newly created NFT when transfered to recipientNFT (sent to IERC721Receiver callback)
bytes returnData;
// hook address for the pool (optional)
address hook;
// hook data (optional)
bytes mintHookData;
}
/// @notice Params for swapAndIncreaseLiquidity() function
struct SwapAndIncreaseLiquidityParams {
uint256 tokenId;
// how much is provided of token0 and token1
uint256 amount0;
uint256 amount1;
address recipient; // recipient of leftover tokens
uint256 deadline;
// source token for swaps (maybe either address(0), token0, token1 or another token)
// if swapSourceToken is another token than token0 or token1 -> amountIn0 + amountIn1 of swapSourceToken are expected to be available
Currency swapSourceToken;
// if swapSourceToken needs to be swapped to token0 - set values
uint256 amountIn0;
uint256 amountOut0Min;
bytes swapData0;
// if swapSourceToken needs to be swapped to token1 - set values
uint256 amountIn1;
uint256 amountOut1Min;
bytes swapData1;
// min amount to be added after swap
uint256 amountAddMin0;
uint256 amountAddMin1;
// hook data for all operations which decrease liquidity (optional)
bytes decreaseLiquidityHookData;
// hook data for all operations which increase liquidity (optional)
bytes increaseLiquidityHookData;
}
/// @notice Constructor
/// @param _positionManager Uniswap v4 position manager
/// @param _universalRouter Uniswap Universal Router (for v3/v2 swaps)
/// @param _zeroxAllowanceHolder 0x Protocol AllowanceHolder contract
/// @param _permit2 Permit2 contract
constructor(
IPositionManager _positionManager,
address _universalRouter,
address _zeroxAllowanceHolder,
IPermit2 _permit2
) Swapper(_positionManager, _universalRouter, _zeroxAllowanceHolder) Ownable(msg.sender) {
permit2 = _permit2;
}
/// @notice Execute instruction accessing approved NFT instead of direct safeTransferFrom call from owner
/// @param tokenId Token to process
/// @param instructions Instructions to execute
/// @return newTokenId Id of position (if a new one was created)
function execute(uint256 tokenId, Instructions memory instructions) public returns (uint256 newTokenId) {
_validateCaller(positionManager, tokenId);
// Get position info from V4 PositionManager
(PoolKey memory poolKey,) = positionManager.getPoolAndPositionInfo(tokenId);
// Decrease liquidity and collect fees/tokens
(uint256 amount0, uint256 amount1) = _decreaseLiquidity(
tokenId,
instructions.liquidity,
instructions.deadline,
instructions.amountRemoveMin0,
instructions.amountRemoveMin1,
instructions.decreaseLiquidityHookData
);
// Validate sufficient tokens for swaps
if (amount0 < instructions.amountIn0 || amount1 < instructions.amountIn1) {
revert AmountError();
}
// Execute the requested action
if (instructions.whatToDo == WhatToDo.COMPOUND_FEES) {
_executeCompoundFees(tokenId, poolKey, amount0, amount1, instructions);
} else if (instructions.whatToDo == WhatToDo.CHANGE_RANGE) {
newTokenId = _executeChangeRange(poolKey, amount0, amount1, instructions);
} else if (instructions.whatToDo == WhatToDo.WITHDRAW_AND_COLLECT_AND_SWAP) {
_executeWithdrawAndSwap(tokenId, poolKey, amount0, amount1, instructions);
} else {
revert NotSupportedWhatToDo();
}
}
/// @notice Execute compound fees operation - swap tokens and increase liquidity
function _executeCompoundFees(
uint256 tokenId,
PoolKey memory poolKey,
uint256 amount0,
uint256 amount1,
Instructions memory instructions
) internal {
uint128 liquidity;
Currency targetToken = instructions.targetToken;
if (targetToken == poolKey.currency0) {
// Swap token1 to token0 and increase liquidity
(liquidity, amount0, amount1) = _swapAndIncrease(
SwapAndIncreaseLiquidityParams(
tokenId,
amount0,
amount1,
instructions.recipient,
instructions.deadline,
poolKey.currency1,
instructions.amountIn1,
instructions.amountOut1Min,
instructions.swapData1,
0,
0,
"",
instructions.amountAddMin0,
instructions.amountAddMin1,
"",
instructions.increaseLiquidityHookData
),
poolKey.currency0,
poolKey.currency1
);
} else if (targetToken == poolKey.currency1) {
// Swap token0 to token1 and increase liquidity
(liquidity, amount0, amount1) = _swapAndIncrease(
SwapAndIncreaseLiquidityParams(
tokenId,
amount0,
amount1,
instructions.recipient,
instructions.deadline,
poolKey.currency0,
0,
0,
"",
instructions.amountIn0,
instructions.amountOut0Min,
instructions.swapData0,
instructions.amountAddMin0,
instructions.amountAddMin1,
"",
instructions.increaseLiquidityHookData
),
poolKey.currency0,
poolKey.currency1
);
} else {
// No swap - increase liquidity with both tokens
(liquidity, amount0, amount1) = _swapAndIncrease(
SwapAndIncreaseLiquidityParams(
tokenId,
amount0,
amount1,
instructions.recipient,
instructions.deadline,
CurrencyLibrary.ADDRESS_ZERO,
0,
0,
"",
0,
0,
"",
instructions.amountAddMin0,
instructions.amountAddMin1,
"",
instructions.increaseLiquidityHookData
),
poolKey.currency0,
poolKey.currency1
);
}
emit CompoundFees(tokenId, liquidity, amount0, amount1);
}
/// @notice Execute change range operation - swap tokens and mint new position
function _executeChangeRange(
PoolKey memory poolKey,
uint256 amount0,
uint256 amount1,
Instructions memory instructions
) internal returns (uint256 newTokenId) {
Currency targetToken = instructions.targetToken;
if (targetToken == poolKey.currency0) {
// Swap token1 to token0 and mint new position
(newTokenId,,,) = _swapAndMint(
SwapAndMintParams(
poolKey.currency0,
poolKey.currency1,
instructions.fee,
instructions.tickSpacing,
instructions.tickLower,
instructions.tickUpper,
amount0,
amount1,
instructions.recipient,
instructions.recipientNFT,
instructions.deadline,
poolKey.currency1,
instructions.amountIn1,
instructions.amountOut1Min,
instructions.swapData1,
0,
0,
"",
instructions.amountAddMin0,
instructions.amountAddMin1,
instructions.swapAndMintReturnData,
instructions.hook,
instructions.increaseLiquidityHookData
)
);
} else if (targetToken == poolKey.currency1) {
// Swap token0 to token1 and mint new position
(newTokenId,,,) = _swapAndMint(
SwapAndMintParams(
poolKey.currency0,
poolKey.currency1,
instructions.fee,
instructions.tickSpacing,
instructions.tickLower,
instructions.tickUpper,
amount0,
amount1,
instructions.recipient,
instructions.recipientNFT,
instructions.deadline,
poolKey.currency0,
0,
0,
"",
instructions.amountIn0,
instructions.amountOut0Min,
instructions.swapData0,
instructions.amountAddMin0,
instructions.amountAddMin1,
instructions.swapAndMintReturnData,
instructions.hook,
instructions.increaseLiquidityHookData
)
);
} else {
// No swap - mint new position with both tokens
(newTokenId,,,) = _swapAndMint(
SwapAndMintParams(
poolKey.currency0,
poolKey.currency1,
instructions.fee,
instructions.tickSpacing,
instructions.tickLower,
instructions.tickUpper,
amount0,
amount1,
instructions.recipient,
instructions.recipientNFT,
instructions.deadline,
CurrencyLibrary.ADDRESS_ZERO,
0,
0,
"",
0,
0,
"",
instructions.amountAddMin0,
instructions.amountAddMin1,
instructions.swapAndMintReturnData,
instructions.hook,
instructions.increaseLiquidityHookData
)
);
}
emit ChangeRange(0, newTokenId); // tokenId 0 as original position is handled elsewhere
}
/// @notice Execute withdraw, collect and swap operation
function _executeWithdrawAndSwap(
uint256 tokenId,
PoolKey memory poolKey,
uint256 amount0,
uint256 amount1,
Instructions memory instructions
) internal {
uint256 targetAmount;
Currency targetToken = instructions.targetToken;
// Swap token0 to target if needed
if (!(poolKey.currency0 == targetToken)) {
(uint256 amountInDelta, uint256 amountOutDelta) = _routerSwap(
Swapper.RouterSwapParams(
poolKey.currency0,
targetToken,
instructions.amountIn0,
instructions.amountOut0Min,
instructions.swapData0
)
);
// Return any leftover token0
if (amountInDelta < amount0) {
poolKey.currency0.transfer(instructions.recipient, amount0 - amountInDelta);
}
targetAmount += amountOutDelta;
} else {
targetAmount += amount0;
}
// Swap token1 to target if needed
if (!(poolKey.currency1 == targetToken)) {
(uint256 amountInDelta, uint256 amountOutDelta) = _routerSwap(
Swapper.RouterSwapParams(
poolKey.currency1,
targetToken,
instructions.amountIn1,
instructions.amountOut1Min,
instructions.swapData1
)
);
// Return any leftover token1
if (amountInDelta < amount1) {
poolKey.currency1.transfer(instructions.recipient, amount1 - amountInDelta);
}
targetAmount += amountOutDelta;
} else {
targetAmount += amount1;
}
// Transfer complete target amount to recipient
if (targetAmount != 0) {
targetToken.transfer(instructions.recipient, targetAmount);
}
emit WithdrawAndCollectAndSwap(tokenId, Currency.unwrap(targetToken), targetAmount);
}
/// @notice ERC721 callback function. Called on safeTransferFrom and does manipulation as configured in encoded Instructions parameter.
/// At the end the NFT (and any newly minted NFT) is returned to sender. The leftover tokens are sent to instructions.recipient.
function onERC721Received(address, /*operator*/ address from, uint256 tokenId, bytes calldata data)
external
override
returns (bytes4)
{
// only Uniswap v4 NFTs allowed
if (msg.sender != address(positionManager)) {
revert WrongContract();
}
// not allowed to send to itself
if (from == address(this)) {
revert SelfSend();
}
Instructions memory instructions = abi.decode(data, (Instructions));
execute(tokenId, instructions);
IERC721(address(positionManager)).safeTransferFrom(address(this), from, tokenId, instructions.returnData);
return IERC721Receiver.onERC721Received.selector;
}
/// @notice Swaps amountIn of tokenIn for tokenOut - returning at least minAmountOut
/// @param params Swap configuration
/// @return amountOut Output amount of tokenOut
/// If tokenIn is wrapped native token - both the token or the wrapped token can be sent (the sum of both must be equal to amountIn)
/// Optionally unwraps any wrapped native token and returns native token instead
function swap(SwapParamsV4 calldata params) external payable returns (uint256 amountOut) {
if (params.tokenIn == params.tokenOut) {
revert SameToken();
}
_prepareAddApproved(params.tokenIn, CurrencyLibrary.ADDRESS_ZERO, CurrencyLibrary.ADDRESS_ZERO, params.amountIn, 0, 0);
uint256 amountInDelta;
(amountInDelta, amountOut) = _routerSwap(
Swapper.RouterSwapParams(
params.tokenIn, params.tokenOut, params.amountIn, params.minAmountOut, params.swapData
)
);
// send swapped amount of tokenOut
if (amountOut != 0) {
params.tokenOut.transfer(params.recipient, amountOut);
}
// if not all was swapped - return leftovers of tokenIn
uint256 leftOver = params.amountIn - amountInDelta;
if (leftOver != 0) {
params.tokenIn.transfer(params.recipient, leftOver);
}
}
/// @notice Does 1 or 2 swaps from swapSourceToken to token0 and token1 and adds as much as possible liquidity to a newly minted position. Newly minted NFT and leftover tokens are returned to recipient.
/// @param params Swap and mint configuration
/// @return tokenId The ID of the token that represents the minted position
/// @return liquidity The amount of liquidity for this position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function swapAndMint(SwapAndMintParams calldata params)
external
payable
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)
{
if (params.token0 == params.token1) {
revert SameToken();
}
_prepareAddApproved(
params.token0,
params.token1,
params.swapSourceToken,
params.amount0,
params.amount1,
params.amountIn0 + params.amountIn1
);
(tokenId, liquidity, amount0, amount1) = _swapAndMint(params);
}
/// @notice Does 1 or 2 swaps from swapSourceToken to token0 and token1 and adds as much as possible liquidity to any existing position (no need to be position owner). Sends any leftover tokens to recipient.
/// @param params Swap and increase liquidity configuration
/// @return liquidity The amount of liquidity added
/// @return amount0 The amount of token0 added
/// @return amount1 The amount of token1 added
function swapAndIncreaseLiquidity(SwapAndIncreaseLiquidityParams memory params)
external
payable
returns (uint128 liquidity, uint256 amount0, uint256 amount1)
{
// first fees must be removed
(uint256 fees0, uint256 fees1) = _decreaseLiquidity(params.tokenId, 0, params.deadline, 0, 0, params.decreaseLiquidityHookData);
// Get position info from V4 PositionManager
(PoolKey memory poolKey,) = positionManager.getPoolAndPositionInfo(params.tokenId);
_prepareAddApproved(
poolKey.currency0,
poolKey.currency1,
params.swapSourceToken,
params.amount0,
params.amount1,
params.amountIn0 + params.amountIn1
);
// if native token special handling - see _decreaseLiquidity()
params.amount0 = params.amount0 + fees0;
params.amount1 = params.amount1 + fees1;
(liquidity, amount0, amount1) = _swapAndIncrease(params, poolKey.currency0, poolKey.currency1);
}
// Internal helper functions
function _prepareAddApproved(
Currency token0,
Currency token1,
Currency otherToken,
uint256 amount0,
uint256 amount1,
uint256 amountOther
) internal {
// Process each token
_prepareAddApprovedToken(token0, amount0);
_prepareAddApprovedToken(token1, amount1);
if (Currency.unwrap(otherToken) != Currency.unwrap(token0) && Currency.unwrap(otherToken) != Currency.unwrap(token1)) {
_prepareAddApprovedToken(otherToken, amountOther);
}
}
function _prepareAddApprovedToken(Currency token, uint256 amount) internal {
if (amount == 0) return;
if (!token.isAddressZero()) {
SafeERC20.safeTransferFrom(IERC20(Currency.unwrap(token)), msg.sender, address(this), amount);
}
}
/// @notice Add SWEEP params if native ETH is involved
/// @param params_array The params array to modify
/// @param hasNativeETH Whether native ETH is involved
function _addSweepParamsIfNeeded(bytes[] memory params_array, bool hasNativeETH) internal view {
if (hasNativeETH) {
params_array[2] = abi.encode(CurrencyLibrary.ADDRESS_ZERO, address(this));
}
}
// swap and mint logic
function _swapAndMint(SwapAndMintParams memory params)
internal
returns (uint256 tokenId, uint128 liquidity, uint256 added0, uint256 added1)
{
(uint256 total0, uint256 total1) = _swapAndPrepareAmounts(params);
// V4 uses different approach - need to create PoolKey and use modifyLiquidities
PoolKey memory poolKey = PoolKey({
currency0: params.token0,
currency1: params.token1,
fee: params.fee,
tickSpacing: params.tickSpacing, // Use dynamic tickSpacing from params
hooks: IHooks(params.hook) // Use hook from params
});
(bytes memory actions, bytes[] memory params_array) = _buildActionsForIncreasingLiquidity(
uint8(Actions.MINT_POSITION),
params.token0,
params.token1
);
liquidity = _calculateLiquidity(
params.tickLower,
params.tickUpper,
poolKey,
total0,
total1
);
params_array[0] = abi.encode(
poolKey,
params.tickLower,
params.tickUpper,
liquidity, // liquidity
total0, // amount0Max
total1, // amount1Max
address(this), // recipient
bytes("") // hookData
);
// Mint position and handle transfers
tokenId = _mintPositionAndTransfer(
actions,
params_array,
params.deadline,
params.recipientNFT,
params.returnData
);
// Calculate consumption and return leftovers
{
uint256 finalBalance0 = poolKey.currency0.balanceOfSelf();
uint256 finalBalance1 = poolKey.currency1.balanceOfSelf();
// Calculate amounts actually added (prevent underflow if balance bigger)
added0 = total0 >= finalBalance0 ? total0 - finalBalance0 : 0;
added1 = total1 >= finalBalance1 ? total1 - finalBalance1 : 0;
// Check minimum amounts were added
if (added0 < params.amountAddMin0) {
revert InsufficientAmountAdded();
}
if (added1 < params.amountAddMin1) {
revert InsufficientAmountAdded();
}
emit SwapAndMint(tokenId, liquidity, added0, added1);
// Return leftover tokens
if (finalBalance0 != 0) {
params.token0.transfer(params.recipient, finalBalance0);
}
if (finalBalance1 != 0) {
params.token1.transfer(params.recipient, finalBalance1);
}
}
}
// Helper function to mint position and transfer NFT
function _mintPositionAndTransfer(
bytes memory actions,
bytes[] memory params_array,
uint256 deadline,
address recipientNFT,
bytes memory returnData
) private returns (uint256 tokenId) {
positionManager.modifyLiquidities{value: address(this).balance}(abi.encode(actions, params_array), deadline);
// Get the newly minted token ID
tokenId = positionManager.nextTokenId() - 1;
// Transfer NFT to recipient
IERC721(address(positionManager)).safeTransferFrom(address(this), recipientNFT, tokenId, returnData);
}
// swap and increase logic
function _swapAndIncrease(SwapAndIncreaseLiquidityParams memory params, Currency token0, Currency token1)
internal
returns (uint128 liquidity, uint256 added0, uint256 added1)
{
(uint256 total0, uint256 total1) = _swapAndPrepareAmounts(
SwapAndMintParams(
token0,
token1,
0,
0,
0,
0,
params.amount0,
params.amount1,
params.recipient,
params.recipient,
params.deadline,
params.swapSourceToken,
params.amountIn0,
params.amountOut0Min,
params.swapData0,
params.amountIn1,
params.amountOut1Min,
params.swapData1,
params.amountAddMin0,
params.amountAddMin1,
"",
address(0), // No hook for increase liquidity
""
)
);
// Get position info to determine currencies for TAKE_PAIR
(PoolKey memory poolKey, PositionInfo info) = positionManager.getPoolAndPositionInfo(params.tokenId);
// Build actions for native ETH if needed
(bytes memory actions, bytes[] memory params_array) = _buildActionsForIncreasingLiquidity(
uint8(Actions.INCREASE_LIQUIDITY),
poolKey.currency0,
poolKey.currency1
);
// Calculate liquidity from amounts
liquidity = _calculateLiquidity(
info.tickLower(),
info.tickUpper(),
poolKey,
total0,
total1
);
params_array[0] = abi.encode(
params.tokenId,
liquidity,
uint128(total0),
uint128(total1),
params.increaseLiquidityHookData
);
params_array[1] = abi.encode(poolKey.currency0, poolKey.currency1, address(this));
positionManager.modifyLiquidities{value: address(this).balance}(abi.encode(actions, params_array), params.deadline);
// Calculate consumption and return leftovers
{
uint256 finalBalance0 = poolKey.currency0.balanceOfSelf();
uint256 finalBalance1 = poolKey.currency1.balanceOfSelf();
added0 = total0 - finalBalance0;
added1 = total1 - finalBalance1;
// Check minimum amounts were added
if (added0 < params.amountAddMin0) {
revert InsufficientAmountAdded();
}
if (added1 < params.amountAddMin1) {
revert InsufficientAmountAdded();
}
emit SwapAndIncreaseLiquidity(params.tokenId, liquidity, added0, added1);
// Return leftover tokens
if (finalBalance0 != 0) {
token0.transfer(params.recipient, finalBalance0);
}
if (finalBalance1 != 0) {
token1.transfer(params.recipient, finalBalance1);
}
}
}
// swaps available tokens and prepares max amounts to be added to positionManager
function _swapAndPrepareAmounts(SwapAndMintParams memory params)
internal
returns (uint256 total0, uint256 total1)
{
Currency swapSource = params.swapSourceToken;
if (swapSource == params.token0) {
if (params.amount0 < params.amountIn1) {
revert AmountError();
}
(uint256 amountInDelta, uint256 amountOutDelta) = _routerSwap(
Swapper.RouterSwapParams(
params.token0, params.token1, params.amountIn1, params.amountOut1Min, params.swapData1
)
);
total0 = params.amount0 - amountInDelta;
total1 = params.amount1 + amountOutDelta;
} else if (swapSource == params.token1) {
if (params.amount1 < params.amountIn0) {
revert AmountError();
}
(uint256 amountInDelta, uint256 amountOutDelta) = _routerSwap(
Swapper.RouterSwapParams(
params.token1, params.token0, params.amountIn0, params.amountOut0Min, params.swapData0
)
);
total1 = params.amount1 - amountInDelta;
total0 = params.amount0 + amountOutDelta;
} else {
(uint256 amountInDelta0, uint256 amountOutDelta0) = _routerSwap(
Swapper.RouterSwapParams(
swapSource, params.token0, params.amountIn0, params.amountOut0Min, params.swapData0
)
);
(uint256 amountInDelta1, uint256 amountOutDelta1) = _routerSwap(
Swapper.RouterSwapParams(
swapSource, params.token1, params.amountIn1, params.amountOut1Min, params.swapData1
)
);
total0 = params.amount0 + amountOutDelta0;
total1 = params.amount1 + amountOutDelta1;
// return third token leftover if any
uint256 leftOver = params.amountIn0 + params.amountIn1 - amountInDelta0 - amountInDelta1;
if (leftOver != 0) {
swapSource.transfer(params.recipient, leftOver);
}
}
_handleApproval(permit2, params.token0, total0);
_handleApproval(permit2, params.token1, total1);
}
// decreases liquidity from uniswap v4 position
function _decreaseLiquidity(
uint256 tokenId,
uint128 liquidity,
uint256 deadline,
uint256 token0Min,
uint256 token1Min,
bytes memory hookData
) internal returns (uint256 amount0, uint256 amount1) {
// Get position info to determine currencies for TAKE_PAIR
(PoolKey memory poolKey,) = positionManager.getPoolAndPositionInfo(tokenId);
// Cache currencies to save gas
Currency currency0 = poolKey.currency0;
Currency currency1 = poolKey.currency1;
// check balance before decreasing liquidity
amount0 = currency0.balanceOfSelf();
amount1 = currency1.balanceOfSelf();
// V4 uses different approach - need to use modifyLiquidities with encoded actions
// Include both DECREASE_LIQUIDITY and TAKE_PAIR actions
bytes memory actions = abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR));
bytes[] memory params_array = new bytes[](2);
params_array[0] = abi.encode(
tokenId,
uint256(liquidity),
uint128(token0Min), // amount0Min
uint128(token1Min), // amount1Min
hookData
);
params_array[1] = abi.encode(currency0, currency1, address(this));
positionManager.modifyLiquidities(abi.encode(actions, params_array), deadline);
// calculate delta
amount0 = currency0.balanceOfSelf() - amount0;
amount1 = currency1.balanceOfSelf() - amount1;
}
}"
},
"lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
"
},
"lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
},
"lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
"
},
"lib/uniswap-hooks/lib/v4-periphery/lib/permit2/src/interfaces/IPermit2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISignatureTransfer} from "./ISignatureTransfer.sol";
import {IAllowanceTransfer} from "./IAllowanceTransfer.sol";
/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer.
/// @dev Users must approve Permit2 before calling any of the transfer functions.
interface IPermit2 is ISignatureTransfer, IAllowanceTransfer {
// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval.
}
"
},
"lib/uniswap-hooks/lib/v4-core/src/interfaces/IHooks.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
/// @notice The hook called before the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @return bytes4 The function selector for the hook
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
/// @notice The hook called after the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @param tick The current tick after the state of a pool is initialized
/// @return bytes4 The function selector for the hook
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
returns (bytes4);
/// @notice The hook called before liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
external
returns (bytes4, BeforeSwapDelta, uint24);
/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external returns (bytes4, int128);
/// @notice The hook called before donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function afterDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
}
"
},
"lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
import {INotifier} from "./INotifier.sol";
import {IImmutableState} from "./IImmutableState.sol";
import {IERC721Permit_v4} from "./IERC721Permit_v4.sol";
import {IEIP712_v4} from "./IEIP712_v4.sol";
import {IMulticall_v4} from "./IMulticall_v4.sol";
import {IPoolInitializer_v4} from "./IPoolInitializer_v4.sol";
import {IUnorderedNonce} from "./IUnorderedNonce.sol";
import {IPermit2Forwarder} from "./IPermit2Forwarder.sol";
/// @title IPositionManager
/// @notice Interface for the PositionManager contract
interface IPositionManager is
INotifier,
IImmutableState,
IERC721Permit_v4,
IEIP712_v4,
IMulticall_v4,
IPoolInitializer_v4,
IUnorderedNonce,
IPermit2Forwarder
{
/// @notice Thrown when the caller is not approved to modify a position
error NotApproved(address caller);
/// @notice Thrown when the block.timestamp exceeds the user-provided deadline
error DeadlinePassed(uint256 deadline);
/// @notice Thrown when calling transfer, subscribe, or unsubscribe when the PoolManager is unlocked.
/// @dev This is to prevent hooks from being able to trigger notifications at the same time the position is being modified.
error PoolManagerMustBeLocked();
/// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity
/// @dev This is the standard entrypoint for the PositionManager
/// @param unlockData is an encoding of actions, and parameters for those actions
/// @param deadline is the deadline for the batched actions to be executed
function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable;
/// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager
/// @dev This must be called by a contract that has already unlocked the v4 PoolManager
/// @param actions the actions to perform
/// @param params the parameters to provide for the actions
function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable;
/// @notice Used to get the ID that will be used for the next minted liquidity position
/// @return uint256 The next token ID
function nextTokenId() external view returns (uint256);
/// @notice Returns the liquidity of a position
/// @param tokenId the ERC721 tokenId
/// @return liquidity the position's liquidity, as a liquidityAmount
/// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library
function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity);
/// @notice Returns the pool key and position info of a position
/// @param tokenId the ERC721 tokenId
/// @return poolKey the pool key of the position
/// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo);
/// @notice Returns the position info of a position
/// @param tokenId the ERC721 tokenId
/// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
function positionInfo(uint256 tokenId) external view returns (PositionInfo);
}
"
},
"lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionInfoLibrary.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
/**
* @dev PositionInfo is a packed version of solidity structure.
* Using the packaged version saves gas and memory by not storing the structure fields in memory slots.
*
* Layout:
* 200 bits poolId | 24 bits tickUpper | 24 bits tickLower | 8 bits hasSubscriber
*
* Fields in the direction from the least significant bit:
*
* A flag to know if the tokenId is subscribed to an address
* uint8 hasSubscriber;
*
* The tickUpper of the position
* int24 tickUpper;
*
* The tickLower of the position
* int24 tickLower;
*
* The truncated poolId. Truncates a bytes32 value so the most signifcant (highest) 200 bits are used.
* bytes25 poolId;
*
* Note: If more bits are needed, hasSubscriber can be a single bit.
*
*/
type PositionInfo is uint256;
using PositionInfoLibrary for PositionInfo global;
library PositionInfoLibrary {
PositionInfo internal constant EMPTY_POSITION_INFO = PositionInfo.wrap(0);
uint256 internal constant MASK_UPPER_200_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000;
uint256 internal constant MASK_8_BITS = 0xFF;
uint24 internal constant MASK_24_BITS = 0xFFFFFF;
uint256 internal constant SET_UNSUBSCRIBE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00;
uint256 internal constant SET_SUBSCRIBE = 0x01;
uint8 internal constant TICK_LOWER_OFFSET = 8;
uint8 internal constant TICK_UPPER_OFFSET = 32;
/// @dev This poolId is NOT compatible with the poolId used in UniswapV4 core. It is truncated to 25 bytes, and just used to lookup PoolKey in the poolKeys mapping.
function poolId(PositionInfo info) internal pure returns (bytes25 _poolId) {
assembly ("memory-safe") {
_poolId := and(MASK_UPPER_200_BITS, info)
}
}
function tickLower(PositionInfo info) internal pure returns (int24 _tickLower) {
assembly ("memory-safe") {
_tickLower := signextend(2, shr(TICK_LOWER_OFFSET, info))
}
}
function tickUpper(PositionInfo info) internal pure returns (int24 _tickUpper) {
assembly ("memory-safe") {
_tickUpper := signextend(2, shr(TICK_UPPER_OFFSET, info))
}
}
function hasSubscriber(PositionInfo info) internal pure returns (bool _hasSubscriber) {
assembly ("memory-safe") {
_hasSubscriber := and(MASK_8_BITS, info)
}
}
/// @dev this does not actually set any storage
function setSubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
assembly ("memory-safe") {
_info := or(info, SET_SUBSCRIBE)
}
}
/// @dev this does not actually set any storage
function setUnsubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
assembly ("memory-safe") {
_info := and(info, SET_UNSUBSCRIBE)
}
}
/// @notice Creates the default PositionInfo struct
/// @dev Called when minting a new position
/// @param _poolKey the pool key of the position
/// @param _tickLower the lower tick of the position
/// @param _ti
Submitted on: 2025-10-10 09:18:28
Comments
Log in to comment.
No comments yet.