Final Contract
Below we give an example of a fully functioning code: a contract that can custody the Algebra NFT positions and manipulate positions and liquidity in them by charging fees, increasing or decreasing liquidity and creating new positions.
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.8.20;
import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol';
import '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol';
import '@cryptoalgebra/integral-periphery/contracts/libraries/TransferHelper.sol';
import '@cryptoalgebra/integral-periphery/contracts/interfaces/INonfungiblePositionManager.sol';
import '@cryptoalgebra/integral-periphery/contracts/base/LiquidityManagement.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
contract LiquidityExamples is IERC721Receiver, LiquidityManagement {
address public constant DAI = 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063;
address public constant USDC = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;
INonfungiblePositionManager public immutable nonfungiblePositionManager;
/// @notice Represents the deposit of an NFT
struct Deposit {
address owner;
uint128 liquidity;
address token0;
address token1;
}
/// @dev deposits[tokenId] => Deposit
mapping(uint256 tokenId => Deposit) public deposits;
constructor(
INonfungiblePositionManager _nonfungiblePositionManager,
address _factory,
address _WMATIC,
address _poolDeployer
) PeripheryImmutableState(_factory, _WMATIC, _poolDeployer) {
nonfungiblePositionManager = _nonfungiblePositionManager;
}
// Implementing `onERC721Received` so this contract can receive custody of erc721 tokens
function onERC721Received(
address operator,
address,
uint256 tokenId,
bytes calldata
) external override returns (bytes4) {
// get position information
_createDeposit(operator, tokenId);
return this.onERC721Received.selector;
}
function _createDeposit(address owner, uint256 tokenId) internal {
(, , address token0, address token1, , , uint128 liquidity, , , , ) =
nonfungiblePositionManager.positions(tokenId);
// set the owner and data for position
// operator is msg.sender
deposits[tokenId] = Deposit({owner: owner, liquidity: liquidity, token0: token0, token1: token1});
}
/// @notice Calls the mint function defined in periphery, mints the same amount of each token.
/// For this example we are providing 1000 DAI and 1000 USDC in liquidity
/// @return tokenId The id of the newly minted ERC721
/// @return liquidity The amount of liquidity for the position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mintNewPosition()
external
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
// For this example, we will provide equal amounts of liquidity in both assets.
// Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
uint256 amount0ToMint = 1000;
uint256 amount1ToMint = 1000;
// transfer tokens to contract
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amount0ToMint);
TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amount1ToMint);
// Approve the position manager
TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), amount0ToMint);
TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), amount1ToMint);
INonfungiblePositionManager.MintParams memory params =
INonfungiblePositionManager.MintParams({
token0: DAI,
token1: USDC,
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
amount0Desired: amount0ToMint,
amount1Desired: amount1ToMint,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});
// Note that the pool defined by DAI/USDC must already be created and initialized in order to mint
(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
// Create a deposit
_createDeposit(msg.sender, tokenId);
// Remove allowance and refund in both assets.
if (amount0 < amount0ToMint) {
TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
uint256 refund0 = amount0ToMint - amount0;
TransferHelper.safeTransfer(DAI, msg.sender, refund0);
}
if (amount1 < amount1ToMint) {
TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
uint256 refund1 = amount1ToMint - amount1;
TransferHelper.safeTransfer(USDC, msg.sender, refund1);
}
}
/// @notice Collects the fees associated with provided liquidity
/// @dev The contract must hold the erc721 token before it can collect fees
/// @param tokenId The id of the erc721 token
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collectAllFees(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
// Caller must own the ERC721 position, meaning it must be a deposit
// set amount0Max and amount1Max to uint256.max to collect all fees
// alternatively can set recipient to msg.sender and avoid another transaction in `sendToOwner`
INonfungiblePositionManager.CollectParams memory params =
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
});
(amount0, amount1) = nonfungiblePositionManager.collect(params);
// send collected feed back to owner
_sendToOwner(tokenId, amount0, amount1);
}
/// @notice A function that decreases the current liquidity by half. An example to show how to call the `decreaseLiquidity` function defined in periphery.
/// @param tokenId The id of the erc721 token
/// @return amount0 The amount received back in token0
/// @return amount1 The amount returned back in token1
function decreaseLiquidityInHalf(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
// caller must be the owner of the NFT
require(msg.sender == deposits[tokenId].owner, 'Not the owner');
// get liquidity data for tokenId
uint128 liquidity = deposits[tokenId].liquidity;
uint128 halfLiquidity = liquidity / 2;
// amount0Min and amount1Min are price slippage checks
// if the amount received after burning is not greater than these minimums, transaction will fail
INonfungiblePositionManager.DecreaseLiquidityParams memory params =
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: halfLiquidity,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
});
(amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params);
//send liquidity back to owner
_sendToOwner(tokenId, amount0, amount1);
}
/// @notice Increases liquidity in the current range
/// @dev Pool must be initialized already to add liquidity
/// @param tokenId The id of the erc721 token
/// @param amount0 The amount to add of token0
/// @param amount1 The amount to add of token1
function increaseLiquidityCurrentRange(
uint256 tokenId,
uint256 amountAdd0,
uint256 amountAdd1
)
external
returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
) {
TransferHelper.safeTransferFrom(deposits[tokenId].token0, msg.sender, address(this), amountAdd0);
TransferHelper.safeTransferFrom(deposits[tokenId].token1, msg.sender, address(this), amountAdd1);
TransferHelper.safeApprove(deposits[tokenId].token0, address(nonfungiblePositionManager), amountAdd0);
TransferHelper.safeApprove(deposits[tokenId].token1, address(nonfungiblePositionManager), amountAdd1);
INonfungiblePositionManager.IncreaseLiquidityParams memory params = INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amountAdd0,
amount1Desired: amountAdd1,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
});
(liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(params);
}
/// @notice Transfers funds to owner of NFT
/// @param tokenId The id of the erc721
/// @param amount0 The amount of token0
/// @param amount1 The amount of token1
function _sendToOwner(
uint256 tokenId,
uint256 amount0,
uint256 amount1
) internal {
// get owner of contract
address owner = deposits[tokenId].owner;
address token0 = deposits[tokenId].token0;
address token1 = deposits[tokenId].token1;
// send collected fees to owner
TransferHelper.safeTransfer(token0, owner, amount0);
TransferHelper.safeTransfer(token1, owner, amount1);
}
/// @notice Transfers the NFT to the owner
/// @param tokenId The id of the erc721
function retrieveNFT(uint256 tokenId) external {
// must be the owner of the NFT
require(msg.sender == deposits[tokenId].owner, 'Not the owner');
// transfer ownership to original owner
nonfungiblePositionManager.safeTransferFrom(address(this), msg.sender, tokenId);
//remove information related to tokenId
delete deposits[tokenId];
}
}