Contract Name:
UniV3LiquidityLocker
Contract Source Code:
File 1 of 1 : UniV3LiquidityLocker
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC721 {
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function ownerOf(uint256 tokenId) external view returns (address);
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
}
interface IERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
interface INonfungiblePositionManager is IERC721 {
struct CollectParams {
address recipient;
uint256 tokenId;
uint128 amount0Max;
uint128 amount1Max;
}
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
}
abstract contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
_transferOwnership(initialOwner);
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == msg.sender, "Ownable: caller is not the owner");
_;
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
contract UniV3LiquidityLocker is Ownable, IERC721Receiver {
INonfungiblePositionManager public immutable uniswapV3NFT;
struct LockedPosition {
address owner;
uint256 unlockTime;
}
mapping(uint256 => LockedPosition) public lockedPositions;
event LiquidityLocked(address indexed user, uint256 indexed tokenId, uint256 unlockTime);
event LiquidityUnlocked(address indexed user, uint256 indexed tokenId);
event FeesCollected(address indexed user, uint256 indexed tokenId, uint256 amount0, uint256 amount1);
event NFTApproved(address approvedTo);
event CollectionApprovalSet(address indexed contractAddress, bool approved);
constructor(address _uniswapV3NFT) Ownable(msg.sender) {
uniswapV3NFT = INonfungiblePositionManager(_uniswapV3NFT);
}
function lockLiquidity(uint256 tokenId, uint256 lockDuration) external {
require(lockDuration >= 5 minutes, "Minimum lock duration is 5 minutes");
require(uniswapV3NFT.ownerOf(tokenId) == msg.sender, "You must own the LP NFT");
uniswapV3NFT.safeTransferFrom(msg.sender, address(this), tokenId);
lockedPositions[tokenId] = LockedPosition({
owner: msg.sender,
unlockTime: block.timestamp + lockDuration
});
emit LiquidityLocked(msg.sender, tokenId, block.timestamp + lockDuration);
}
function unlockLiquidity(uint256 tokenId) external {
LockedPosition storage position = lockedPositions[tokenId];
require(position.owner == msg.sender, "Not the owner of this NFT");
require(block.timestamp >= position.unlockTime, "Liquidity is still locked");
uniswapV3NFT.safeTransferFrom(address(this), msg.sender, tokenId);
delete lockedPositions[tokenId];
emit LiquidityUnlocked(msg.sender, tokenId);
}
/**
* @dev Grants UniswapV3 Position Manager full approval to manage NFTs owned by this contract.
* This only needs to be called once per deployment.
*/
function setApprovalForCollection() external onlyOwner {
uniswapV3NFT.setApprovalForAll(address(uniswapV3NFT), true);
emit CollectionApprovalSet(address(uniswapV3NFT), true);
}
/**
* @dev Collects fees from a locked NFT position.
* @param tokenId The ID of the NFT.
* @param amount0Max Maximum amount of token0 to collect.
* @param amount1Max Maximum amount of token1 to collect.
* @param recipient Address that will receive the fees.
*/
function collectFees(
uint256 tokenId,
uint128 amount0Max,
uint128 amount1Max,
address recipient
) external payable {
require(lockedPositions[tokenId].owner == msg.sender, "Not the owner of this NFT");
INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
recipient: recipient,
tokenId: tokenId,
amount0Max: amount0Max,
amount1Max: amount1Max
});
(uint256 amount0, uint256 amount1) = uniswapV3NFT.collect{value: msg.value}(params);
require(amount0 > 0 || amount1 > 0, "No fees available");
emit FeesCollected(recipient, tokenId, amount0, amount1);
}
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return this.onERC721Received.selector;
}
}