// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {HasSignature} from "../core/HasSignature.sol"; /** * Contract for the activity of NFT claim stage 2. */ interface IClaimAbleNFT { function safeMint(address to, uint256 tokenID) external; } contract NFTClaimStage2 is HasSignature, ReentrancyGuard { struct MintConfig { uint256 parse1MaxSupply; // max supply for phase1 uint256 maxSupply; // max supply for phase2 address currency; // token address which user must pay to mint uint256 mintPrice; // in wei address feeToAddress; // wallet address to receive mint fee } // parse: 0: not open or end, 1: phase1, 2: phase2 uint256 public mintParse = 0; uint256 public immutable _CACHED_CHAIN_ID; address public immutable _CACHED_THIS; address public immutable nftAddress; address public verifier; MintConfig public mintConfig; uint256 public parse1Count; uint256 public totalCount; event NFTClaimed(address indexed nftAddress, address indexed to, uint256[] ids); event ParseUpdated(uint256 _parse); event MintConfigUpdated(MintConfig config); event VerifierUpdated(address indexed verifier); constructor(address _nftAddress, address _verifier, MintConfig memory _mintConfig) { _CACHED_CHAIN_ID = block.chainid; _CACHED_THIS = address(this); nftAddress = _nftAddress; verifier = _verifier; mintConfig = _mintConfig; } modifier whenNotPaused() { require(mintParse > 0, "NFTClaimer: not begin or ended"); _; } function updateMintParse(uint256 _mintParse) external onlyOwner { mintParse = _mintParse; emit ParseUpdated(_mintParse); } function updateMintConfig(MintConfig calldata config) external onlyOwner { mintConfig = config; emit MintConfigUpdated(config); } /** * @dev update verifier address */ function updateVerifier(address _verifier) external onlyOwner { require(_verifier != address(0), "NFTClaimer: address can not be zero"); verifier = _verifier; emit VerifierUpdated(_verifier); } /** * @dev claim NFT * Get whitelist signature from a third-party service, then call this method to claim NFT * @param saltNonce nonce * @param signature signature */ function claim( uint256[] memory ids, uint256 tokenAmount, uint256 saltNonce, bytes calldata signature ) external nonReentrant whenNotPaused { // get current parse; uint256 count = ids.length; require(count > 0, "NFTClaimer: ids length must be greater than 0"); if (mintParse == 1) { require(count <= mintConfig.parse1MaxSupply - parse1Count, "NFTClaimer: exceed parse 1 max supply"); } else { require(count <= mintConfig.maxSupply - totalCount, "NFTClaimer: exceed max supply"); } require(tokenAmount >= mintConfig.mintPrice * count, "NFTClaimer: insufficient token amount"); address to = _msgSender(); bytes32 criteriaMessageHash = getMessageHash( to, nftAddress, ids, tokenAmount, _CACHED_THIS, _CACHED_CHAIN_ID, saltNonce ); checkSigner(verifier, criteriaMessageHash, signature); IERC20(mintConfig.currency).transferFrom(to, mintConfig.feeToAddress, tokenAmount); for (uint256 i = 0; i < count; ++i) { IClaimAbleNFT(nftAddress).safeMint(to, ids[i]); } // require(count > 2, "run to here"); totalCount += count; if (mintParse == 1) { parse1Count += count; } _useSignature(signature); emit NFTClaimed(nftAddress, to, ids); } function getMessageHash( address _to, address _address, uint256[] memory _ids, uint256 _tokenAmount, address _contract, uint256 _chainId, uint256 _saltNonce ) public pure returns (bytes32) { bytes memory encoded = abi.encodePacked(_to, _address, _tokenAmount, _contract, _chainId, _saltNonce); for (uint256 i = 0; i < _ids.length; ++i) { encoded = bytes.concat(encoded, abi.encodePacked(_ids[i])); } return keccak256(encoded); } }