1+ // SPDX-License-Identifier: Apache-2.0
2+ pragma solidity ^ 0.8.0 ;
3+
4+ import "./interface/IDropSinglePhase.sol " ;
5+ import "../lib/MerkleProof.sol " ;
6+ import "@openzeppelin/contracts-upgradeable/utils/structs/BitMapsUpgradeable.sol " ;
7+
8+ abstract contract DropSinglePhase is IDropSinglePhase {
9+
10+ using BitMapsUpgradeable for BitMapsUpgradeable.BitMap;
11+
12+ /// @dev The active conditions for claiming lazy minted tokens.
13+ ClaimCondition public claimCondition;
14+
15+ mapping (bytes32 => mapping (address => uint256 )) private lastClaimTimestamp;
16+ mapping (bytes32 => BitMapsUpgradeable.BitMap) private usedAllowlistSpot;
17+
18+ /// @dev The ID for the active claim condition.
19+ bytes32 private conditionId;
20+
21+ function _msgSender () internal virtual returns (address ) {
22+ return msg .sender ;
23+ }
24+
25+ function _beforeClaim (
26+ address _receiver ,
27+ uint256 _quantity ,
28+ address _currency ,
29+ uint256 _pricePerToken ,
30+ AllowlistProof calldata _allowlistProof ,
31+ bytes memory _data
32+ ) internal virtual ;
33+
34+ function _afterClaim (
35+ address _receiver ,
36+ uint256 _quantity ,
37+ address _currency ,
38+ uint256 _pricePerToken ,
39+ AllowlistProof calldata _allowlistProof ,
40+ bytes memory _data
41+ ) internal virtual ;
42+
43+ /// @dev Lets an account claim NFTs.
44+ function claim (
45+ address _receiver ,
46+ uint256 _quantity ,
47+ address _currency ,
48+ uint256 _pricePerToken ,
49+ AllowlistProof calldata _allowlistProof ,
50+ bytes memory _data
51+ ) external payable virtual {
52+
53+ _beforeClaim (_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data);
54+
55+ bytes32 activeConditionId = conditionId;
56+ /**
57+ * We make allowlist checks (i.e. verifyClaimMerkleProof) before verifying the claim's general
58+ * validity (i.e. verifyClaim) because we give precedence to the check of allow list quantity
59+ * restriction over the check of the general claim condition's quantityLimitPerTransaction
60+ * restriction.
61+ */
62+
63+ // Verify inclusion in allowlist.
64+ (bool validMerkleProof , uint256 merkleProofIndex ) = verifyClaimMerkleProof (
65+ _msgSender (),
66+ _quantity,
67+ _allowlistProof
68+ );
69+
70+ // Verify claim validity. If not valid, revert.
71+ bool toVerifyMaxQuantityPerTransaction = _allowlistProof.maxQuantityInAllowlist == 0 ;
72+
73+ verifyClaim (
74+ _msgSender (),
75+ _quantity,
76+ _currency,
77+ _pricePerToken,
78+ toVerifyMaxQuantityPerTransaction
79+ );
80+
81+ if (validMerkleProof && _allowlistProof.maxQuantityInAllowlist > 0 ) {
82+ /**
83+ * Mark the claimer's use of their position in the allowlist. A spot in an allowlist
84+ * can be used only once.
85+ */
86+ usedAllowlistSpot[activeConditionId].set (merkleProofIndex);
87+ }
88+
89+ // Update contract state.
90+ claimCondition.supplyClaimed += _quantity;
91+ lastClaimTimestamp[activeConditionId][_msgSender ()] = block .timestamp ;
92+
93+ // If there's a price, collect price.
94+ collectPrice (_quantity, _currency, _pricePerToken);
95+
96+ // Mint the relevant NFTs to claimer.
97+ uint256 startTokenId = transferClaimedTokens (_receiver, _quantity);
98+
99+ emit TokensClaimed (claimCondition, _msgSender (), _receiver, _quantity, startTokenId);
100+
101+ _afterClaim (_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data);
102+ }
103+
104+ /// @dev Collects and distributes the primary sale value of NFTs being claimed.
105+ function collectPrice (
106+ uint256 _quantityToClaim ,
107+ address _currency ,
108+ uint256 _pricePerToken
109+ ) internal virtual ;
110+
111+ /// @dev Transfers the NFTs being claimed.
112+ function transferClaimedTokens (
113+ address _to ,
114+ uint256 _quantityBeingClaimed
115+ ) internal virtual returns (uint256 startTokenId );
116+
117+ function setClaimCondition (ClaimCondition calldata _condition , bool _resetClaimEligibility )
118+ external
119+ {
120+ if (_resetClaimEligibility) {
121+ conditionId = keccak256 (abi.encodePacked (msg .sender , block .number ));
122+ }
123+
124+ ClaimCondition memory currentConditoin = claimCondition;
125+
126+ claimCondition = ClaimCondition ({
127+ startTimestamp: block .timestamp ,
128+ maxClaimableSupply: _condition.maxClaimableSupply,
129+ supplyClaimed: _resetClaimEligibility ? currentConditoin.supplyClaimed : _condition.supplyClaimed,
130+ quantityLimitPerTransaction: _condition.supplyClaimed,
131+ waitTimeInSecondsBetweenClaims: _condition.waitTimeInSecondsBetweenClaims,
132+ merkleRoot: _condition.merkleRoot,
133+ pricePerToken: _condition.pricePerToken,
134+ currency: _condition.currency
135+ });
136+
137+ emit ClaimConditionUpdated (_condition, _resetClaimEligibility);
138+ }
139+
140+ /// @dev Checks a request to claim NFTs against the active claim condition's criteria.
141+ function verifyClaim (
142+ address _claimer ,
143+ uint256 _quantity ,
144+ address _currency ,
145+ uint256 _pricePerToken ,
146+ bool verifyMaxQuantityPerTransaction
147+ ) public view {
148+ ClaimCondition memory currentClaimPhase = claimCondition;
149+
150+ require (
151+ _currency == currentClaimPhase.currency && _pricePerToken == currentClaimPhase.pricePerToken,
152+ "invalid currency or price. "
153+ );
154+
155+ // If we're checking for an allowlist quantity restriction, ignore the general quantity restriction.
156+ require (
157+ _quantity > 0 &&
158+ (! verifyMaxQuantityPerTransaction || _quantity <= currentClaimPhase.quantityLimitPerTransaction),
159+ "invalid quantity. "
160+ );
161+ require (
162+ currentClaimPhase.supplyClaimed + _quantity <= currentClaimPhase.maxClaimableSupply,
163+ "exceed max claimable supply. "
164+ );
165+ // require(nextTokenIdToClaim + _quantity <= nextTokenIdToMint, "not enough minted tokens.");
166+ // require(maxTotalSupply == 0 || nextTokenIdToClaim + _quantity <= maxTotalSupply, "exceed max total supply.");
167+ // require(
168+ // maxWalletClaimCount == 0 || walletClaimCount[_claimer] + _quantity <= maxWalletClaimCount,
169+ // "exceed claim limit"
170+ // );
171+
172+ uint256 timestampOfLastClaim = lastClaimTimestamp[conditionId][_claimer];
173+ require (timestampOfLastClaim == 0 || block .timestamp >= timestampOfLastClaim + currentClaimPhase.waitTimeInSecondsBetweenClaims, "cannot claim. " );
174+ }
175+
176+ /// @dev Checks whether a claimer meets the claim condition's allowlist criteria.
177+ function verifyClaimMerkleProof (
178+ address _claimer ,
179+ uint256 _quantity ,
180+ AllowlistProof calldata _allowlistProof
181+ ) public view returns (bool validMerkleProof , uint256 merkleProofIndex ) {
182+ ClaimCondition memory currentClaimPhase = claimCondition;
183+
184+ if (currentClaimPhase.merkleRoot != bytes32 (0 )) {
185+ (validMerkleProof, merkleProofIndex) = MerkleProof.verify (
186+ _allowlistProof.proof,
187+ currentClaimPhase.merkleRoot,
188+ keccak256 (abi.encodePacked (_claimer, _allowlistProof.maxQuantityInAllowlist))
189+ );
190+ require (validMerkleProof, "not in whitelist. " );
191+ require (! usedAllowlistSpot[conditionId].get (merkleProofIndex), "proof claimed. " );
192+ require (
193+ _allowlistProof.maxQuantityInAllowlist == 0 || _quantity <= _allowlistProof.maxQuantityInAllowlist,
194+ "invalid quantity proof. "
195+ );
196+ }
197+ }
198+
199+ }
0 commit comments