@@ -43,8 +43,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
4343 error OwnerMismatch ();
4444
4545 // Events
46- event Init (string social , string website , uint256 multiple , uint256 closingDelay , uint256 unstakingFee , uint256 closingTime , uint256 dealMinimum , uint256 dealMaximum , uint256 deliveryType , bool active , bool transferable );
47- event Setup (address escrowToken , uint256 closingDelay , uint256 unstakingFee , string website , string social , string image , string description , uint256 deliveryType );
46+ event Init (string social , string website , uint256 multiple , uint256 closingDelay , uint256 unstakingFee , uint256 closingTime , uint256 dealMinimum , uint256 dealMaximum , uint256 deliveryType , bool active , bool transferable , bool timeBasedClosing );
47+ event Setup (address escrowToken , uint256 closingDelay , uint256 unstakingFee , uint256 dealMinimum , uint256 dealMaximum , string website , string social , string image , string description , uint256 deliveryType );
4848 event Configure (string description , string social , string website , uint256 closingTime , uint256 dealMinimum , uint256 dealMaximum , uint256 multiple );
4949 event StateUpdated (State state );
5050 event Transferable (bool transferable );
@@ -81,6 +81,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
8181 IWhitelist public stakersWhitelist;
8282 IWhitelist public claimsWhitelist;
8383
84+ uint256 lastStakeTimestamp;
8485 mapping (uint256 tokenId = > uint256 ) public stakedAmount;
8586 mapping (uint256 tokenId = > uint256 ) public claimedAmount;
8687 mapping (address staker = > uint256 ) public stakes;
@@ -110,6 +111,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
110111 bool active;
111112 bool cancelled;
112113 bool transferable;
114+ bool timeBasedClosing;
113115 }
114116
115117 Configuration private config;
@@ -142,7 +144,9 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
142144 if (bytes (symbol_).length == 0 ) revert ZeroDetected ();
143145
144146 if (config_.sponsor == ADDRESS_ZERO) revert ZeroDetected ();
145- _validClosingTime (config_.closingTime, config_.closingDelay);
147+ if (config_.timeBasedClosing) {
148+ _validClosingTime (config_.closingTime, config_.closingDelay);
149+ }
146150 if (config_.dealMinimum > config_.dealMaximum) revert BadStakesRange ();
147151 if (config_.multiple < 1e18 ) revert ZeroDetected ();
148152
@@ -168,7 +172,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
168172 config_.dealMaximum,
169173 config_.deliveryType,
170174 config_.active,
171- config_.transferable
175+ config_.transferable,
176+ config_.timeBasedClosing
172177 );
173178 }
174179
@@ -207,24 +212,29 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
207212 address escrowToken_ ,
208213 uint256 closingDelay_ ,
209214 uint256 unstakingFee_ ,
215+ uint256 dealMinimum_ ,
216+ uint256 dealMaximum_ ,
210217 string memory social_ ,
211218 string memory website_ ,
212219 string memory image_ ,
213220 string memory description_ ,
214221 uint256 deliveryType_
215222 ) external onlySponsor {
216223 if (state () != State.Setup) revert CannotSetup ();
224+ if (dealMinimum_ > dealMaximum_) revert BadStakesRange ();
217225
218226 config.escrowToken = escrowToken_;
219227 config.closingDelay = closingDelay_;
220228 config.unstakingFee = unstakingFee_;
229+ config.dealMinimum = dealMinimum_;
230+ config.dealMaximum = dealMaximum_;
221231 config.social = social_;
222232 config.website = website_;
223233 config.image = image_;
224234 config.description = description_;
225235 config.deliveryType = deliveryType_;
226236
227- emit Setup (escrowToken_, closingDelay_, unstakingFee_, website_, social_, image_, description_, deliveryType_);
237+ emit Setup (escrowToken_, closingDelay_, unstakingFee_, dealMaximum_, dealMinimum_, website_, social_, image_, description_, deliveryType_);
228238 }
229239
230240 /**
@@ -255,17 +265,22 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
255265 uint256 dealMaximum_ ,
256266 uint256 multiple_
257267 ) external onlySponsor {
258- _canConfigure ();
259- _validClosingTime (closingTime_, config.closingDelay);
260- if (dealMinimum_ > dealMaximum_) revert BadStakesRange ();
268+ _canConfigure ();
261269 if (multiple_ < 1e18 ) revert ZeroDetected ();
262270
271+ if (config.timeBasedClosing) {
272+ _validClosingTime (closingTime_, config.closingDelay);
273+ if (dealMinimum_ > dealMaximum_) revert BadStakesRange ();
274+
275+ config.dealMinimum = dealMinimum_;
276+ config.dealMaximum = dealMaximum_;
277+ config.closingTime = closingTime_;
278+ }
279+
263280 config.description = description_;
264281 config.social = social_;
265282 config.website = website_;
266- config.closingTime = closingTime_;
267- config.dealMinimum = dealMinimum_;
268- config.dealMaximum = dealMaximum_;
283+
269284 config.multiple = multiple_;
270285
271286 emit Configure (description_, social_, website_, closingTime_, dealMinimum_, dealMaximum_, multiple_);
@@ -302,7 +317,12 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
302317 */
303318 function setTransferable (bool transferable_ ) external onlyArbitrator {
304319 if (state () == State.Cancelled) revert CannotConfigure ();
305- if (_afterClosed ()) revert CannotConfigure ();
320+
321+ if (config.timeBasedClosing) {
322+ if (_afterClosed (config.closingTime)) revert CannotConfigure ();
323+ } else {
324+ if (_afterClosed (lastStakeTimestamp)) revert CannotConfigure ();
325+ }
306326
307327 config.transferable = transferable_;
308328 emit Transferable (transferable_);
@@ -374,7 +394,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
374394 if (state () < State.Claiming) revert CannotRecover ();
375395
376396 if (state () == State.Claiming) {
377- if (_totalStaked (_tokenId) >= config.dealMinimum ) revert MinimumReached ();
397+ if (_minimumReached () ) revert MinimumReached ();
378398 }
379399
380400 AccountV3TBD tokenBoundAccount = getTokenBoundAccount (tokenId);
@@ -449,17 +469,31 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
449469 */
450470 function state () public view returns (State) {
451471 if (config.cancelled) return State.Cancelled;
472+ if (_isClaimed ()) return State.Closed;
452473
453- if (_beforeClose ()) {
454- if (config.active) return State.Active;
455- return State.Setup ;
456- }
474+ if (config.timeBasedClosing) {
475+ if (_afterClosed ( config.closingTime)) {
476+ return State.Cancelled ;
477+ }
457478
458- if (_afterClosed ()) return State.Closed;
479+ if (_beforeClose ()) {
480+ if (config.active) return State.Active;
481+ return State.Setup;
482+ }
459483
460- if (_isClaimed ()) return State.Closed;
484+ return State.Claiming;
485+ } else {
486+ if (_minimumReached ()) {
487+ if (_afterClosed (lastStakeTimestamp)){
488+ return State.Cancelled;
489+ }
490+
491+ return State.Claiming;
492+ }
461493
462- return State.Claiming;
494+ if (config.active) return State.Active;
495+ return State.Setup;
496+ }
463497 }
464498
465499 /**
@@ -557,6 +591,13 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
557591 return config;
558592 }
559593
594+ /**
595+ * @notice Check if the minimum has been reached
596+ */
597+ function _minimumReached () private view returns (bool ) {
598+ return _totalStaked (_tokenId) >= config.dealMinimum;
599+ }
600+
560601 /**
561602 * @notice Check if all tokens have been claimed by the sponsor
562603 */
@@ -574,8 +615,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
574615 /**
575616 * @notice Check if the current time is after closing time
576617 */
577- function _afterClosed () private view returns (bool ) {
578- return config.closingTime > 0 && block .timestamp > (config.closingTime + CLAIMING_PERIOD);
618+ function _afterClosed (uint256 since ) private view returns (bool ) {
619+ return since > 0 && block .timestamp > (since + CLAIMING_PERIOD);
579620 }
580621
581622 /**
@@ -625,7 +666,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
625666 function _canClaim () internal view {
626667 if (_claimId == _tokenId) revert TokenOutOfBounds ();
627668 if (state () != State.Claiming) revert NotInClaimingState ();
628- if (_totalStaked (_tokenId) < config.dealMinimum ) revert MinimumNotReached ();
669+ if (! _minimumReached () ) revert MinimumNotReached ();
629670 }
630671
631672 /**
@@ -634,14 +675,16 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
634675 function _canConfigure () internal view {
635676 if (state () >= State.Closed) revert CannotConfigure ();
636677 if (state () == State.Claiming) {
637- if (_totalStaked (_tokenId) >= config.dealMinimum ) revert MinimumReached ();
678+ if (_minimumReached () ) revert MinimumReached ();
638679 }
639680 }
640681
641682 function _validateActivation () internal view {
642683 if (config.escrowToken == ADDRESS_ZERO) revert ZeroDetected ();
643- if (config.closingDelay <= 0 ) revert ZeroDetected ();
644- if (config.closingDelay > MAX_CLOSING_RANGE) revert ClosingDelayTooBig ();
684+ if (config.timeBasedClosing) {
685+ if (config.closingDelay <= 0 ) revert ZeroDetected ();
686+ if (config.closingDelay > MAX_CLOSING_RANGE) revert ClosingDelayTooBig ();
687+ }
645688 if (config.unstakingFee > MAX_FEE) revert ClosingFeeTooBig ();
646689 if (bytes (config.website).length == 0 ) revert ZeroDetected ();
647690 if (bytes (config.social).length == 0 ) revert ZeroDetected ();
@@ -670,6 +713,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
670713 uint256 newTokenId = _tokenId++ ;
671714 stakedAmount[newTokenId] = amount;
672715 stakes[staker] = currentStake;
716+ lastStakeTimestamp = block .timestamp ;
673717
674718 _safeMint (staker, newTokenId);
675719
0 commit comments