1+ // SPDX-License-Identifier: MIT
2+ pragma solidity >= 0.8.20 ;
3+ pragma abicoder v2;
4+
5+
6+ import "@openzeppelin/contracts/access/Ownable.sol " ;
7+ // import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol" as TH;
8+ import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol " ;
9+ // import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
10+ import "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
11+ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol " ;
12+ import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol " ;
13+ // import "@openzeppelin/[email protected] /interfaces/IERC4626.sol"; 14+ // import "@openzeppelin/contracts/utils/math/SafeMath.sol";
15+
16+
17+
18+ library TransferHelper {
19+ /// @notice Transfers tokens from the targeted address to the given destination
20+ /// @notice Errors with 'STF' if transfer fails
21+ /// @param token The contract address of the token to be transferred
22+ /// @param from The originating address from which the tokens will be transferred
23+ /// @param to The destination address of the transfer
24+ /// @param value The amount to be transferred
25+ function safeTransferFrom (
26+ address token ,
27+ address from ,
28+ address to ,
29+ uint256 value
30+ ) internal {
31+ (bool success , bytes memory data ) =
32+ token.call (abi.encodeWithSelector (IERC20 .transferFrom.selector , from, to, value));
33+ require (success && (data.length == 0 || abi.decode (data, (bool ))), 'STF ' );
34+ }
35+
36+ /// @notice Transfers tokens from msg.sender to a recipient
37+ /// @dev Errors with ST if transfer fails
38+ /// @param token The contract address of the token which will be transferred
39+ /// @param to The recipient of the transfer
40+ /// @param value The value of the transfer
41+ function safeTransfer (
42+ address token ,
43+ address to ,
44+ uint256 value
45+ ) internal {
46+ (bool success , bytes memory data ) = token.call (abi.encodeWithSelector (IERC20 .transfer.selector , to, value));
47+ require (success && (data.length == 0 || abi.decode (data, (bool ))), 'ST ' );
48+ }
49+
50+ /// @notice Approves the stipulated contract to spend the given allowance in the given token
51+ /// @dev Errors with 'SA' if transfer fails
52+ /// @param token The contract address of the token to be approved
53+ /// @param to The target of the approval
54+ /// @param value The amount of the given token the target will be allowed to spend
55+ function safeApprove (
56+ address token ,
57+ address to ,
58+ uint256 value
59+ ) internal {
60+ (bool success , bytes memory data ) = token.call (abi.encodeWithSelector (IERC20 .approve.selector , to, value));
61+ require (success && (data.length == 0 || abi.decode (data, (bool ))), 'SA ' );
62+ }
63+
64+ /// @notice Transfers ETH to the recipient address
65+ /// @dev Fails with `STE`
66+ /// @param to The destination of the transfer
67+ /// @param value The value to be transferred
68+ function safeTransferETH (address to , uint256 value ) internal {
69+ (bool success , ) = to.call {value: value}(new bytes (0 ));
70+ require (success, 'STE ' );
71+ }
72+ }
73+
74+
75+ interface INonfungiblePositionManager {
76+
77+ function positions (uint256 tokenId )
78+ external
79+ view
80+ returns (
81+ uint96 nonce ,
82+ address operator ,
83+ address token0 ,
84+ address token1 ,
85+ uint24 fee ,
86+ int24 tickLower ,
87+ int24 tickUpper ,
88+ uint128 liquidity ,
89+ uint256 feeGrowthInside0LastX128 ,
90+ uint256 feeGrowthInside1LastX128 ,
91+ uint128 tokensOwed0 ,
92+ uint128 tokensOwed1
93+ );
94+
95+ struct CollectParams {
96+ uint256 tokenId;
97+ address recipient;
98+ uint128 amount0Max;
99+ uint128 amount1Max;
100+ }
101+
102+ function collect (CollectParams calldata params ) external payable returns (uint256 amount0 , uint256 amount1 );
103+
104+ function transferFrom (address from , address to , uint256 tokenId ) external ;
105+ function safeTransferFrom (address from , address to , uint256 tokenId ) external ;
106+ }
107+
108+ contract FeeCollectorV3PositionTimeLockV1 is IERC721Receiver , Ownable , ReentrancyGuard {
109+ uint256 public defaultLockDuration = 7 days ;
110+
111+ struct LockedPosition {
112+ INonfungiblePositionManager positionManager;
113+ address positionOwner;
114+ uint256 tokenId;
115+ uint256 releaseTime;
116+ }
117+
118+ // mapping(address => mapping(address => uint256)) public userLockedPositionsTokenId;
119+ mapping (address => mapping (bytes32 => LockedPosition)) public lockedPositions;
120+
121+ // INonfungiblePositionManager public positionManager;
122+
123+ constructor (address initialOwner ) Ownable (initialOwner) {}
124+
125+ event FeesCollected (address indexed user , address indexed positionManager , uint256 tokenId , uint256 amount0 , uint256 amount1 );
126+ event PositionLocked (address indexed user , address indexed positionManager , uint256 tokenId , uint256 releaseTime );
127+ event PositionUnlocked (address indexed user , address indexed positionManager , uint256 tokenId );
128+
129+ function onERC721Received (
130+ address _operator ,
131+ address _from ,
132+ uint256 _tokenId ,
133+ bytes calldata _data ) external pure override returns (bytes4 ) {
134+ // require(address(whitelistedNftContract) == _msgSender());
135+ // _stakeNft(tokenId, from);
136+ return IERC721Receiver .onERC721Received.selector ;
137+ }
138+
139+
140+ function _hashLockedPositionKey (address _positionManager , uint256 tokenId ) private view returns (bytes32 ) {
141+ return keccak256 (abi.encodePacked (_msgSender (), _positionManager, tokenId));
142+ }
143+
144+ function collectFees (address _positionManager , uint256 tokenId , address recipient ) external nonReentrant returns (uint256 amount0Collected , uint256 amount1Collected ) {
145+ LockedPosition storage lockedPosition = lockedPositions[_msgSender ()][_hashLockedPositionKey (_positionManager, tokenId)];
146+ require (lockedPosition.positionOwner == _msgSender (), "failed to load position " );
147+
148+ INonfungiblePositionManager positionManager = lockedPosition.positionManager;
149+ INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams ({
150+ tokenId: lockedPosition.tokenId,
151+ recipient: recipient,
152+ amount0Max: type (uint128 ).max,
153+ amount1Max: type (uint128 ).max
154+ });
155+
156+ (amount0Collected, amount1Collected) = positionManager.collect (params);
157+ emit FeesCollected (_msgSender (), _positionManager, tokenId, amount0Collected, amount1Collected);
158+
159+ return (amount0Collected, amount1Collected);
160+ }
161+
162+ function lockPosition (address _positionManager , uint256 tokenId , uint256 lockDurationInSeconds ) external {
163+ uint256 _duration = lockDurationInSeconds;
164+ if (lockDurationInSeconds == 0 ) {
165+ _duration = defaultLockDuration;
166+ }
167+ // LockedPosition storage lockedposition = lockedPositions[_msgSender()][_hashLockedPositionKey(_positionManager, tokenId)];
168+ // require(lockedposition.tokenId != 0, "failed position already locked");
169+ uint256 releaseTime = (block .timestamp + (_duration));
170+ require (releaseTime > block .timestamp , "Release time must be in the future " );
171+
172+ INonfungiblePositionManager positionManager = INonfungiblePositionManager (_positionManager);
173+ positionManager.safeTransferFrom (_msgSender (), address (this ), tokenId);
174+
175+ lockedPositions[_msgSender ()][_hashLockedPositionKey (_positionManager, tokenId)] = LockedPosition (positionManager, _msgSender (), tokenId, releaseTime);
176+
177+ emit PositionLocked (_msgSender (), _positionManager, tokenId, releaseTime);
178+ }
179+
180+ function unlockPosition (address _positionManager , uint256 tokenId ) external {
181+ LockedPosition storage lockedPosition = lockedPositions[_msgSender ()][_hashLockedPositionKey (_positionManager, tokenId)];
182+ require (lockedPosition.positionOwner == _msgSender (), "failed to load position, not Owner " );
183+ require (block .timestamp >= lockedPosition.releaseTime, "not in time to unlock " );
184+
185+ INonfungiblePositionManager positionManager = lockedPosition.positionManager;
186+ positionManager.safeTransferFrom (address (this ), _msgSender (), tokenId);
187+
188+ delete lockedPositions[_msgSender ()][_hashLockedPositionKey (_positionManager, tokenId)];
189+ }
190+
191+
192+ }
0 commit comments