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