pragma solidity ^0.8.4;





contract Tao is ERC721A, Ownable, ReentrancyGuard {
    using Strings for uint256;
    struct Identity {
        bytes32 r;
        bytes32 s;
        uint8 v;
    }
    enum IdentityType {
        SuperOG,
        OG,
        FreeMintWL,
        PreSaleWL
    }
    enum Status {
        Pending,
        FreeMint,
        PreSaleMint,
        PublicSale,
        Finished
    }
    Status public status;
    address private _Signer;
    address private _publicSigner;
    string public _baseTokenURI;
    uint256 public immutable maxSupply;
    uint8 public  MAX_SUPEROG_FREEMINTS_PER_ADDRESS = 3;
    uint8 public  MAX_OG_FREEMINTS_PER_ADDRESS = 2;
    uint8 public  MAX_WL_FREEMINTS_PER_ADDRESS = 1;
    uint8 public  MAX_PRESALEWL_MINTS_PER_ADDRESS = 2;
    uint8 public  MAX_PUBLICSALE_MINTS_PER_ADDRESS = 3;
    uint256 public FreeMintSupply = 1088;
    uint256 public PreSaleSupply = 3000;
    uint256 public PublicSaleSupply = 1800;
    uint256 public FreeMinted = 0;
    uint256 public PreSaleMinted = 0;
    uint256 public PublicSaleMinted = 0;
    uint256 public constant PRICEPRESALE = 0.0266 ether;
    uint256 public constant PRICEPUBILCSALE = 0.0388 ether;

    event StatusChanged(Status status);

    //publicMinted
    mapping (address => uint256) public PublicMinted;

    constructor(
        uint256 _maxSupply,
        address  Signer,
        address PublicSigner
    ) ERC721A ("Tao","TAO") {
        maxSupply = _maxSupply;
        _Signer = Signer;
        _publicSigner = PublicSigner;
    }

    modifier callerIsUser() {
        require(tx.origin == msg.sender, "The caller is another contract");
         _;
     }
    modifier FreeMint(uint256 num) {
         require(status == Status.FreeMint, "FreeMint not Started");
         require(FreeMinted + num <= FreeMintSupply, "FreeMint Supply Exceeded");
         require(totalSupply() + num <= maxSupply, "Exceeds Total Supply");
         _;
    }
   
    function _FreeMintSuperOG(uint256 quantity, Identity memory identity) external callerIsUser FreeMint(quantity) {
        require(numberMinted(msg.sender) + quantity <= MAX_SUPEROG_FREEMINTS_PER_ADDRESS, "Max mint amount per wallet exceeded.");
        bytes32 digest = keccak256(abi.encode(IdentityType.SuperOG, msg.sender));
        require(_isVerifiedIdentity(digest, identity), "address is not allowlisted");
        _safeMint(msg.sender, quantity);
        FreeMinted += quantity;
    }

    function _FreeMintOG(uint256 quantity, Identity memory identity) external callerIsUser FreeMint(quantity) {
        require(numberMinted(msg.sender) + quantity <= MAX_OG_FREEMINTS_PER_ADDRESS, "Max mint amount per wallet exceeded.");
        bytes32 digest = keccak256(abi.encode(IdentityType.OG, msg.sender));
        require(_isVerifiedIdentity(digest, identity), "address is not allowlisted");
        _safeMint(msg.sender, quantity);
        FreeMinted += quantity;
    }

    function _FreeMintWL(uint256 quantity, Identity memory identity) external callerIsUser FreeMint(quantity) {
        require(numberMinted(msg.sender) + quantity <= MAX_WL_FREEMINTS_PER_ADDRESS, "Max mint amount per wallet exceeded.");
        bytes32 digest = keccak256(abi.encode(IdentityType.FreeMintWL, msg.sender));
        require(_isVerifiedIdentity(digest, identity), "address is not allowlisted");
        _safeMint(msg.sender, quantity);
        FreeMinted += quantity;
    }
    
    function _PreSaleMint(uint256 quantity, Identity memory identity) external payable callerIsUser {
        require(status == Status.PreSaleMint, "PreSaleMint not started");
        require(msg.value >= PRICEPRESALE * quantity, "need more eth");
        require(numberMinted(msg.sender) + quantity <= MAX_PRESALEWL_MINTS_PER_ADDRESS, "Max mint amount per wallet exceeded.");
        require(PreSaleMinted + quantity <= PreSaleSupply, "PreSaleSupply Exceeded");
        require(totalSupply() + quantity <= maxSupply, "Exceeds total supply");
        bytes32 digest = keccak256(abi.encode(IdentityType.PreSaleWL, msg.sender));
        require(_isVerifiedIdentity(digest, identity), "address is not allowlisted");
         _safeMint(msg.sender, quantity);
        PreSaleMinted += quantity;
    }

    function _PublicSaleMint(uint256 quantity, bytes32 hash, bytes memory signature) external payable callerIsUser {
        require(status == Status.PublicSale, "PublicSale not started");
        require(PublicSaleMinted + quantity <= PublicSaleSupply, "PublicSaleSupply Exceeded");
        require(PublicMinted[msg.sender] + quantity <= MAX_PUBLICSALE_MINTS_PER_ADDRESS, "Max mint amount per wallet exceeded");
        require(msg.value >= PRICEPUBILCSALE * quantity, "need more eth");
        require(recoverSigner(hash, signature) == _publicSigner, "This address is not allowed");
        require(totalSupply() + quantity <= maxSupply, "Exceeds total supply");
         _safeMint(msg.sender, quantity);
        PublicSaleMinted += quantity;
        PublicMinted[msg.sender] += quantity;
    }
    
    function _isVerifiedIdentity(bytes32 digest, Identity memory identity) internal view returns(bool) {
        address signer = ecrecover(digest, identity.v, identity.r, identity.s);
        require(signer != address(0), "ECDSA: invalid signature");
        return signer == _Signer;
    }
    
    function recoverSigner(bytes32 hash, bytes memory signature) internal pure returns(address) {
        bytes32 messageDigest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
        return ECDSA.recover(messageDigest, signature);
    }
    
    function _setStatus(Status _status) external onlyOwner {
        status = _status;
        emit StatusChanged(status);
    }
    
    function numberMinted(address owner) public view returns(uint256) {
        return _numberMinted(owner);
    }

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
            return string(abi.encodePacked(
                _baseTokenURI, 
                tokenId.toString(),
                ".json"
                ));
    }

    function setBaseTokenURI(string memory baseTokenURI) public onlyOwner {
        _baseTokenURI = baseTokenURI;
    }

    function getStatus() public view returns(Status) {
        return status;
    }

    function devMint(uint256 quantity) external onlyOwner {
        require(status == Status.Finished);
        require(totalSupply() + quantity <= maxSupply);
         _safeMint(msg.sender, quantity);
    }

    function withdrawMoney() external onlyOwner nonReentrant {
        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

}
[{"inputs":[{"internalType":"uint256","name":"_maxSupply","type":"uint256"},{"internalType":"address","name":"Signer","type":"address"},{"internalType":"address","name":"PublicSigner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"ApprovalToCurrentOwner","type":"error"},{"inputs":[],"name":"ApproveToCaller","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[],"name":"MintedQueryForZeroAddress","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","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":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"enum Tao.Status","name":"status","type":"uint8"}],"name":"StatusChanged","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":[],"name":"FreeMintSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FreeMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_OG_FREEMINTS_PER_ADDRESS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PRESALEWL_MINTS_PER_ADDRESS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PUBLICSALE_MINTS_PER_ADDRESS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPEROG_FREEMINTS_PER_ADDRESS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_WL_FREEMINTS_PER_ADDRESS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICEPRESALE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICEPUBILCSALE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PreSaleMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PreSaleSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"PublicMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PublicSaleMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PublicSaleSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Tao.Identity","name":"identity","type":"tuple"}],"name":"_FreeMintOG","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Tao.Identity","name":"identity","type":"tuple"}],"name":"_FreeMintSuperOG","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Tao.Identity","name":"identity","type":"tuple"}],"name":"_FreeMintWL","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Tao.Identity","name":"identity","type":"tuple"}],"name":"_PreSaleMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"_PublicSaleMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"_baseTokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum Tao.Status","name":"_status","type":"uint8"}],"name":"_setStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"devMint","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":[],"name":"getStatus","outputs":[{"internalType":"enum Tao.Status","name":"","type":"uint8"}],"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":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"numberMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":[],"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":"nonpayable","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":"nonpayable","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":"baseTokenURI","type":"string"}],"name":"setBaseTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"status","outputs":[{"internalType":"enum Tao.Status","name":"","type":"uint8"}],"stateMutability":"view","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawMoney","outputs":[],"stateMutability":"nonpayable","type":"function"}]