contract GRAYCRAFT2 is ERC721Creator {
    using SafeMath for uint256;
    string public constant GRAYCRAFT_PROVENANCE = "9128513b2c6a083408da8815a69ab3c617c2116350b790ac6ab299aeef2e3d4d";
    address public constant GRAYCRAFT_ADDRESS = 0x9030807ba4C71831808408CbF892bfA1261A6E7D; // GrayCraft 1 address
    uint256 public constant CRAFT_PRICE = 80000000000000000; // 0.08 ETH
    uint256 public constant MAX_CRAFT_PURCHASE = 20; // max purchase per txn
    uint256 public constant EXCLUSIVE_EXPIRY_DATE = 1633003200; // 30th Sep 2021
    uint256 public constant MAX_GRAYCRAFT = 10000; // max of 10,000 Gray Craft 2

    bool public saleIsActive = false; // determines whether sales is active
    uint256 reservedGrayCraft = 500; // reserved for owner/giveaway
    uint256 exclusiveOwnership = 77; // reserved for graycraft 1 owners
    mapping (uint256 => bool) exclusiveClaim; // tokenId => hasClaimed

    event AssetMinted(uint256 indexed tokenId, address owner);

    constructor() ERC721Creator("GRAYCRAFT2", "GRAY2") {}

    /* ========== External public sales functions ========== */

    // @dev give away exclusive supply to owners of GrayCraft1
    function claimExclusive(uint256 tokenId) external nonReentrant {
        require(saleIsActive); // Sale must be active
        require(exclusiveOwnership > 0); // Exceeds exclusive ownership supply
        require(!exclusiveClaim[tokenId], "Has claimed before");
        require(ERC721(GRAYCRAFT_ADDRESS).ownerOf(tokenId) == msg.sender); // Not authorised

        exclusiveClaim[tokenId] = true;

        _mintBase(msg.sender, Strings.toString(_tokenCount+1));
        emit AssetMinted(_tokenCount, msg.sender);

        exclusiveOwnership = exclusiveOwnership.sub(1);
    }

    // @dev mints graycrafts for the general public
    function mintGraycraft(uint numberOfTokens) external payable {
        require(saleIsActive); // Sale must be active
        require(numberOfTokens <= MAX_CRAFT_PURCHASE); // Max mint of 20
        // we ensure that this function mints a maximum of (10000 - 77 = 9923) graycrafts
        // `exclusiveOwnership` decrements with each mint
        // 50 <= 10000 - 76
        require(totalSupply().add(numberOfTokens) <= MAX_GRAYCRAFT.sub(exclusiveOwnership)); // Max supply exceeded
        require(CRAFT_PRICE.mul(numberOfTokens) <= msg.value); // Value sent is not correct
        
        for (uint i = 0; i < numberOfTokens; i++) {
            if (totalSupply() < MAX_GRAYCRAFT) {
                _mintBase(msg.sender, Strings.toString(_tokenCount+1));
                emit AssetMinted(_tokenCount, msg.sender);
            }
        }
    }

    /* ========== External owner functions ========== */

    // @dev withdraw funds
    function withdraw() external onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    // @dev reserve GrayCraft for owner, max of 500
    function reserveGrayCraft(uint256 amount) external onlyOwner {        
        require(amount <= reservedGrayCraft); // Exceeds reserved supply
        uint i;
        for (i = 0; i < amount; i++) {
            _mintBase(msg.sender, Strings.toString(_tokenCount+1));
            emit AssetMinted(_tokenCount, msg.sender);
        }
        reservedGrayCraft = reservedGrayCraft.sub(amount);
    }

    // @dev owners can force claim GrayCraft1 exclusive
    function ownerClaimExclusive(uint256 tokenId) external onlyOwner {
        require(block.timestamp > EXCLUSIVE_EXPIRY_DATE); // Not allowed to claim
        require(saleIsActive); // Sale must be active
        require(exclusiveOwnership > 0); // Exceeds exclusive ownership supply
        require(!exclusiveClaim[tokenId], "Has claimed before");

        exclusiveClaim[tokenId] = true;

        _mintBase(msg.sender, Strings.toString(_tokenCount+1));
        emit AssetMinted(_tokenCount, msg.sender);

        exclusiveOwnership = exclusiveOwnership.sub(1);
    }

    // @dev flips the state for sales
    function flipSaleState() external onlyOwner {
        saleIsActive = !saleIsActive;
    }
}
function flipSaleState() external onlyOwner {
        saleIsActive = !saleIsActive;
    }
// @dev flips the state for sales