contract WAGMISevenTwentyOne is ERC721, Ownable {
    using SafeMath for uint256;

    bool public saleIsActive = false;
    string public PROVENANCE = 'fda87b2427b54fa172bc8a88a8dc952cb6fbfc008516c11d4d986a1eff4d8194';
    string public OPENSEA_STORE_METADATA = 'ipfs://QmZkUbeWgJp19rsFD98tJ7jCdGdXDj16gQwCdKaWNjovUk';

    uint256 public constant PER_TOKEN_PRICE = 50000000000000000;
    uint256 public constant MAX_TOKEN_ALLOWANCE = 25;

    uint256 public MAX_TOTAL_TOKENS;
    uint256 public REVEAL_TIMESTAMP;
    uint256 public startingIndexBlock;
    uint256 public startingIndex;

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _baseURI,
        uint256 _maxNftSupply,
        uint256 _saleStart
    ) ERC721(_name, _symbol) {
        MAX_TOTAL_TOKENS = _maxNftSupply;
        REVEAL_TIMESTAMP = _saleStart;
        _setBaseURI(_baseURI);
    }

    /**
     *  @dev Enable Contract Owner to withdraw funds from the contract
     */
    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        msg.sender.transfer(balance);
    }

    /**
     *  @dev Enable Contract Owner to transfer contract funds to any address of her choosing
     */
    function withdrawTransfer(address _to) public onlyOwner {
        require(address(_to) != address(this), 'Cannot send funds to the contract itself');
        uint256 balance = address(this).balance;
        msg.sender.transfer(balance);
    }

    /**
     *  @dev Set aside tokens for marketing, Owner does not need to pay. Tokens are sent to the Owner.
     */
    function mintTeamTokens(uint256 _numberOfTokens) public onlyOwner {
        require(_numberOfTokens <= MAX_TOTAL_TOKENS, 'Can not mint more than the total supply.');
        require(_numberOfTokens >= 100, 'Can not mint more than 100 NFTs at a time.');
        require(totalSupply().add(_numberOfTokens) <= MAX_TOTAL_TOKENS, 'Minting would exceed max supply of Tokens');
        uint256 supply = totalSupply();
        uint256 i;
        for (i = 0; i < _numberOfTokens; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    /**
     *  @dev Mint any amount of Tokens to another address for free.
     */
    function mintTokenAndTransfer(address _to, uint256 _numberOfTokens) public onlyOwner {
        require(_numberOfTokens <= MAX_TOTAL_TOKENS, 'Can not mint more than the total supply.');
        require(totalSupply().add(_numberOfTokens) <= MAX_TOTAL_TOKENS, 'Minting would exceed max supply of Tokens');
        require(address(_to) != address(this), 'Cannot mint to contract itself.');
        require(address(_to) != address(0), 'Cannot mint to a null address.');
        uint256 supply = totalSupply();
        for (uint256 i = 0; i < _numberOfTokens; i++) {
            if (totalSupply() < MAX_TOTAL_TOKENS) {
                _safeMint(_to, supply + i);
            }
        }
    }

    /**
     *  @dev Set the timestamp for the collection to reveal itself. Likely not in use.
     */
    function setRevealTimestamp(uint256 _revealTimeStamp) public onlyOwner {
        REVEAL_TIMESTAMP = _revealTimeStamp;
    }

    /**
     *  @dev Set the provenance hash
     */
    function setProvenanceHash(string memory _provenanceHash) public onlyOwner {
        require(bytes(PROVENANCE).length == 0, 'Provenance has already been set, no do-overs!');
        PROVENANCE = _provenanceHash;
    }

    /**
     *  @dev Set OpenSea metadata per https://docs.opensea.io/docs/contract-level-metadata
     */
    function setContractURI(string memory _contractMetadataURI) public onlyOwner {
        OPENSEA_STORE_METADATA = _contractMetadataURI;
    }

    /**
     *  @dev Get OpenSea metadata
     */
    function contractURI() public view returns (string memory) {
        return OPENSEA_STORE_METADATA;
    }

    /**
     *  @dev Set the IPFS baseURI, including ending `/` where JSON is located
     */
    function setBaseURI(string memory _baseURI) public onlyOwner {
        _setBaseURI(_baseURI);
    }

    /**
     *  @dev Pause sale if active, make active if paused
     */
    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    /**
     *  @dev Vending part of the contract, any address can purchase Tokens based on the contract price.
     */
    function mintTokens(uint256 _numberOfTokens) public payable {
        require(saleIsActive, 'Sale must be active to mint.');
        require(_numberOfTokens <= MAX_TOKEN_ALLOWANCE, 'Minting allowance is being enforced');
        require(
            totalSupply().add(_numberOfTokens) <= MAX_TOTAL_TOKENS,
            'Purchase would exceed max supply of contract tokens'
        );
        require(PER_TOKEN_PRICE.mul(_numberOfTokens) <= msg.value, 'Incorrect ETH value sent, not enough');

        for (uint256 i = 0; i < _numberOfTokens; i++) {
            uint256 mintIndex = totalSupply();
            if (totalSupply() < MAX_TOTAL_TOKENS) {
                _safeMint(msg.sender, mintIndex);
            }
        }

        /*
            If we haven't set the starting index and this is either 
                1) the last saleable token or 
                2) the first token to be sold after
            the end of pre-sale, set the starting index block
        */
        if (startingIndexBlock == 0 && (totalSupply() == MAX_TOTAL_TOKENS || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }

    /**
     * @dev Set the starting index for the collection
     */
    function setStartingIndex() public {
        require(startingIndex == 0, 'Starting index is already set');
        require(startingIndexBlock != 0, 'Starting index block must be set');
        startingIndex = uint256(blockhash(startingIndexBlock)) % MAX_TOTAL_TOKENS;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint256(blockhash(block.number - 1)) % MAX_TOTAL_TOKENS;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }

    /**
     * @dev Set the starting index block for the collection, essentially unblocking
     * setting starting index
     */
    function emergencySetStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, 'Starting index is already set');
        startingIndexBlock = block.number;
    }
}
/**
 * @title SevenTwentyOneVending.sol
 * @dev Extends ERC721 Non-Fungible Token Standard's basic implementation
 */
function contractURI() public view returns (string memory) {
    return OPENSEA_STORE_METADATA;
}

/**
 *  @dev Get OpenSea metadata
 */