// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

error PrivateMintNotStarted();
error PublicMintNotStarted();
error InsufficientPayment();
error NotInWhitelist();
error ExceedSupply();
error ExceedMaxPerWallet();
error ExceedMaxPerTransaction();

     /*########################################################################################|
     ||                                                                                       ||
     ||                                                                                       ||
     ||                                                                                       ||
     ||                              ####  ####         ####  ####                            ||
     ||                              ####  ####         ####  ####                            ||
     ||                             #####  ####         ####  #####                           ||
     ||                            ############         ############                          ||
     ||                         ###############         ###############                       ||
     ||                       #################         #################                     ||
     ||                   *    ################ ### ### ################    *                 ||
     ||                   *   * ############### ### ### ############### *   *                 ||
     ||                 *  *  * *###################################* *  * **                 ||
     ||                      * *** * **#########################** * **  *                    ||
     ||                        *   ** *  **#################**  * **  **                      ||
     ||                         ##***  ** *  #############  * **  ***##                       ||
     ||                         ######*** ** *###########* ** ***######                       ||
     ||                         ################*     *################                       ||
     ||                         ############### *     * ###############                       ||
     ||                         ###############   * *   ###############                       ||
     ||                         ###############  ** **  ###############                       ||
     ||                        ################  * * *  ################                      ||
     ||                        ################   * *   ################                      ||
     ||                        ################   ***   ################                      ||
     ||                                                                                       ||
     ||                                                                                       ||
     ||     #####  #####   ########   ##########      ########     ########    #########      ||
     ||     #####  #####   ########   ###########    ##### #####   ########   ##### #####     ||
     ||     #####  #####   #####      #####  ####    ####  #####   #####      ##### #####     ||
     ||     ############   ########   ###########    ####  #####   ########    ########       ||
     ||     ############   ########   ##### #####    ####  #####   ########       #######     ||
     ||     #####  #####   #####      #####  ####    ####  #####   #####      #####  ####     ||
     ||     #####  #####   #########  #####  ####    ##### #####   #########  ##### #####     ||
     ||     #####  #####   #########  #####  ####     ########     #########    ########      ||
     ||                                                                                       ||
     ||                                                                                       ||
     |########################################################################################*/

contract CastleHeroes is ERC721A, Ownable, ReentrancyGuard {
    using MerkleProof for bytes32[];

    uint16 private devSupply = 76;
    uint16 private presaleSupply = 1700;
    uint16 private collectionSupply = 1876;

    bool private privateMintStarted;
    bool private publicMintStarted;

    uint8 private presaleMaxItemsPerWallet = 1;
    uint8 private presaleMaxItemsPerTransaction = 1;
    uint8 private maxItemsPerWallet = 1;
    uint8 private maxItemsPerTransaction = 1;

    uint256 private presalePrice = 0 ether;
    uint256 private mintPrice = 0 ether;

    string private baseTokenURI;
    string private contractMetadataURI;

    bytes32 private presaleMerkleRoot;

    constructor() ERC721A("Castle Heroes", "CH") {}

    modifier whenPrivateMint() {
        if (!privateMintStarted || publicMintStarted) revert PrivateMintNotStarted();
        _;
    }

    modifier whenPublicMint() {
        if (!publicMintStarted) revert PublicMintNotStarted();
        _;
    }

    function devMint(uint8 quantity, address _destination) external onlyOwner {
        if(totalSupply() + quantity > devSupply) revert ExceedSupply();
        _mint(_destination, quantity);
    }

    function privateMint(bytes32[] memory proof, uint16 quantity) external payable nonReentrant whenPrivateMint {
        if(msg.value < presalePrice * quantity) revert InsufficientPayment();
        if(totalSupply() + quantity > presaleSupply) revert ExceedSupply();
        if(quantity > presaleMaxItemsPerTransaction) revert ExceedMaxPerTransaction();
        if(_numberMinted(msg.sender) + quantity > presaleMaxItemsPerWallet) revert ExceedMaxPerWallet();
        if(!isAddressWhitelisted(proof, msg.sender)) revert NotInWhitelist();

        _mint(msg.sender, quantity);        
    }

    function mint(uint8 quantity) external payable nonReentrant whenPublicMint {
        if(msg.value < mintPrice * quantity) revert InsufficientPayment();
        if(totalSupply() + quantity > collectionSupply) revert ExceedSupply();
        if(quantity > maxItemsPerTransaction) revert ExceedMaxPerTransaction();
        if(_numberMinted(msg.sender) + quantity > maxItemsPerWallet) revert ExceedMaxPerWallet();

        _mint(msg.sender, quantity);        
    }

    function isAddressWhitelisted(bytes32[] memory proof, address _address) internal view returns (bool) {
        return proof.verify(presaleMerkleRoot, keccak256(abi.encodePacked(_address)));
    }

    function withdraw() external onlyOwner nonReentrant {
        Address.sendValue(payable(owner()), address(this).balance);
    }

    function contractURI() public view returns (string memory) {
        return contractMetadataURI;
    }

    function _baseURI() internal view override(ERC721A) returns (string memory) {
        return baseTokenURI;
    }

    function setBaseTokenURI(string memory value) external onlyOwner {
        baseTokenURI = value;
    }

    function setContractMetadataURI(string memory value) external onlyOwner {
        contractMetadataURI = value;
    }

    function startPrivateMint() external onlyOwner {
        privateMintStarted = true;
    }

    function startPublicMint() external onlyOwner {
        publicMintStarted = true;
    }

    function emergencyStop() external onlyOwner {
        privateMintStarted = false;
        publicMintStarted = false;
    }

    function setPresaleMaxItemsPerWallet(uint8 value) external onlyOwner {
        presaleMaxItemsPerWallet = value;
    }

    function setPresaleMaxItemsPerTransaction(uint8 value) external onlyOwner {
        presaleMaxItemsPerTransaction = value;
    }

    function setMaxItemsPerWallet(uint8 value) external onlyOwner {
        maxItemsPerWallet = value;
    }

    function setMaxItemsPerTransaction(uint8 value) external onlyOwner {
        maxItemsPerTransaction = value;
    }

    function setPresalePrice(uint256 value) external onlyOwner {
        presalePrice = value;
    }

    function setMintPrice(uint256 value) external onlyOwner {
        mintPrice = value;
    }

    function setDevSupply(uint16 value) external onlyOwner {
        devSupply = value;
    }

    function setPresaleSupply(uint16 value) external onlyOwner {
        presaleSupply = value;
    }

    function setCollectionSupply(uint16 value) external onlyOwner {
        collectionSupply = value;
    }

    function setPresaleMerkleRoot(bytes32 value) external onlyOwner {
        presaleMerkleRoot = value;
    }
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"ExceedMaxPerTransaction","type":"error"},{"inputs":[],"name":"ExceedMaxPerWallet","type":"error"},{"inputs":[],"name":"ExceedSupply","type":"error"},{"inputs":[],"name":"InsufficientPayment","type":"error"},{"inputs":[],"name":"MintERC2309QuantityExceedsLimit","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[],"name":"NotInWhitelist","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"OwnershipNotInitializedForExtraData","type":"error"},{"inputs":[],"name":"PrivateMintNotStarted","type":"error"},{"inputs":[],"name":"PublicMintNotStarted","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"URIQueryForNonexistentToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"ConsecutiveTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contractURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"quantity","type":"uint8"},{"internalType":"address","name":"_destination","type":"address"}],"name":"devMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyStop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"quantity","type":"uint8"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"uint16","name":"quantity","type":"uint16"}],"name":"privateMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setBaseTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"value","type":"uint16"}],"name":"setCollectionSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setContractMetadataURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"value","type":"uint16"}],"name":"setDevSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"value","type":"uint8"}],"name":"setMaxItemsPerTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"value","type":"uint8"}],"name":"setMaxItemsPerWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setMintPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"value","type":"uint8"}],"name":"setPresaleMaxItemsPerTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"value","type":"uint8"}],"name":"setPresaleMaxItemsPerWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"value","type":"bytes32"}],"name":"setPresaleMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setPresalePrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"value","type":"uint16"}],"name":"setPresaleSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startPrivateMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startPublicMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]