/// 
///     _ ,  ___     _ _    _ ,          ___        
///   ,- -  -   -_, - - /  - -    -_-/  -   ---___- 
///  _||_  (  ~/||    ('||  ||   (_ /      (' ||    
/// ' ||   (  / ||   (( ||--||  (_ --_    ((  ||    
///   ||    \/==||   (( ||--||    --_ )  ((   ||    
///   |,    /_ _||   (( /   ||   _/  ))   (( //     
/// _-/    (  - \\,    -___-\\, (_-_-       -____-  
///                                                 
///   A SINFUL JOURNEY.                                                


pragma solidity >=0.7.0 <0.9.0;




contract FaustFiends is ERC721, Ownable {
  using Strings for uint256;
  using Counters for Counters.Counter;

  Counters.Counter private supply;

  string public uriPrefix = "";
  string public uriSuffix = ".json";
  string public hiddenMetadataUri;
  
  uint256 public cost;
  uint256 public maxSupply = 5000;
  uint256 public maxMintAmountPerTx = 2;
  uint256 public maxMintAmountPerWhitelistTx = 3;

  bool public whitelistMintEnabled = false;
  bool public publicMintEnabled = false;
  bool public revealed = false;

  mapping(address => bool) whitelist;

  mapping(address => bool) whitelistMinted;
  mapping(address => bool) publicMinted;

  constructor() ERC721("FAUST Fiend", "FIEND") {
    setHiddenMetadataUri("ipfs://QmQvta82BtPyNKE1TFMS4yjS3pdNfxFr7TwaMTqEFtArag/unrevealed.json");
    setCost(0);
    mintExistingHolders();
  }

  modifier onlyWhitelisted() {
    require(isWhitelisted(msg.sender), "You are not whitelisted");
    _;
  }

  modifier hasNotWhitelistMinted() {
    require(!whitelistMinted[msg.sender], "You have already minted during whitelist mint.");
    _;
  }

  modifier hasNotPublicMinted() {
    require(!publicMinted[msg.sender], "You have already minted during public mint.");
    _;
  }

  modifier whitelistMintCompliance(uint256 _mintAmount) {
    require(_mintAmount > 0 && _mintAmount <= maxMintAmountPerWhitelistTx, "Invalid whitelist mint amount!");
    require(supply.current() + _mintAmount <= maxSupply, "Max whitelist supply exceeded!");
    _;
  }

  modifier publicMintCompliance(uint256 _mintAmount) {
    require(_mintAmount > 0 && _mintAmount <= maxMintAmountPerTx, "Invalid mint amount!");
    require(supply.current() + _mintAmount <= maxSupply, "Max supply exceeded!");
    _;
  }

  function isWhitelisted(address _address) public view returns (bool) {
      return whitelist[_address];
  }

  function hasWhitelistMinted(address _address) public view returns (bool) {
      return whitelistMinted[_address];
  }

  function hasPublicMinted(address _address) public view returns (bool) {
      return publicMinted[_address];
  }

    function whitelistAdd(address _address) public onlyOwner {
        whitelist[_address] = true;
    }

    function whitelistAddMultiple(address[] memory users) public onlyOwner {
        for (uint i = 0; i < users.length; i++) {
            whitelist[users[i]] = true;
        }
    }

    function whitelistRemove(address _address) public onlyOwner {
        whitelist[_address] = false;
    }

  function totalSupply() public view returns (uint256) {
    return supply.current();
  }

  function mint(uint256 _mintAmount) public payable publicMintCompliance(_mintAmount) hasNotPublicMinted {
    require(publicMintEnabled, "Public minting not enabled!");
    require(msg.value >= cost * _mintAmount, "Insufficient funds!");

    _mintLoop(msg.sender, _mintAmount);
    publicMinted[msg.sender] = true;
  }

  function whitelistMint(uint256 _mintAmount) public payable whitelistMintCompliance(_mintAmount) onlyWhitelisted hasNotWhitelistMinted{
    require(whitelistMintEnabled, "Whitelist minting not enabled!");
    require(msg.value >= cost * _mintAmount, "Insufficient funds!");

    _mintLoop(msg.sender, _mintAmount);
    whitelistMinted[msg.sender] = true;
  }
  
  function mintForAddress(uint256 _mintAmount, address _receiver) public publicMintCompliance(_mintAmount) onlyOwner {
    _mintLoop(_receiver, _mintAmount);
  }

  function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
    uint256 currentTokenId = 1;
    uint256 ownedTokenIndex = 0;

    while (ownedTokenIndex < ownerTokenCount && currentTokenId <= maxSupply) {
      address currentTokenOwner = ownerOf(currentTokenId);

      if (currentTokenOwner == _owner) {
        ownedTokenIds[ownedTokenIndex] = currentTokenId;

        ownedTokenIndex++;
      }

      currentTokenId++;
    }

    return ownedTokenIds;
  }

  function tokenURI(uint256 _tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(_tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );

    if (revealed == false) {
      return hiddenMetadataUri;
    }

    string memory currentBaseURI = _baseURI();
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), uriSuffix))
        : "";
  }

  function setRevealed(bool _state) public onlyOwner {
    revealed = _state;
  }

  function setCost(uint256 _cost) public onlyOwner {
    cost = _cost;
  }
  
  // POWER UP OUR FIENDS COMING FROM THE FANTOM NETWORK
  function mintExistingHolders() internal {
      require(supply.current() == 0, "Tokens have already been minted!");
      address[81] memory existingHolders = [
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0xCAc7a3aA77b2Cb5a07c9f33c9B7118101770e410,
          0x6524E9B964978b86Ad6AA5B11374864926f0cfa5,
          0xE9d53d1e63925517b9dE2e0d724B91D88ABf6917,
          0xf4b01D989f81382b0690ccf5165A29B406679B00,

          0x4eBa0f6c0436013ae4759d3cC20D5b9970AE7735,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x5F13B17024C14D6D10D5F4c6A04c0877e0C3E50B,

          0x5F13B17024C14D6D10D5F4c6A04c0877e0C3E50B,
          0x08BCb4de5407B97cC1eb05f8331e88D35F74Ab83,
          0x08BCb4de5407B97cC1eb05f8331e88D35F74Ab83,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x33Cf3A4Fe0AeDfE3A85C2845395A438baba7702e,

          0xB5cC3308C8E0F12fCCCa72e0fA3C8C20518c11e7,
          0x4201fD30BC90689Dcb0E36177Cc2485B3177bbaE,
          0x22559E54E1702C27A99E03b8437482fCf1686113,
          0x22559E54E1702C27A99E03b8437482fCf1686113,
          0x7a0EF482134aa53468850A7b117EcA38A6a3003D,

          0x04B7dBD17a817d22eBD9B35A9a210D8e6E59a7CE,
          0xf2d696E7b3e040B160D658dE02C00A4Ef5Be1bC0,
          0xA7E1af033A471d37b7f5A495bF342418822cD979,
          0xA6f051653fB75dC3756f10209E2D0941fcB586e9,
          0x6524E9B964978b86Ad6AA5B11374864926f0cfa5,
          
          0x9eC99B98Fb35aB8bdaB4C37880759816180874De,
          0x4209F11186318DBca71996Fd5842bcA214b3269F,
          0x4553448E66559c825eca1E9B6b1A6D8556e8F584,
          0xA9Ea60Bb786e9F12b29bB44ba73a38418e0A365b,
          0x64C12292045246c4B01e44Ff79763749eC397d70,

          0xe15bE2C6771713077500d29634EB19D20BBb8BB1,
          0x51Ca42fb14C727e2E026Df8dab8Be684bfB3ED9A,
          0xB73C27e8f39CFd94bb1fF6F30Ccf41351E989df9,
          0x21E61C2905C0B1d3760984Bb5dC19915557e3Ed4,
          0x9448D4fDfAB5Ae18302c49ab8599EC744A0e743C,
          
          0xFFB671f107328B9dc46BA9aa5c9396D63CB95488,
          0x21E61C2905C0B1d3760984Bb5dC19915557e3Ed4,
          0x21E61C2905C0B1d3760984Bb5dC19915557e3Ed4,
          0x9448D4fDfAB5Ae18302c49ab8599EC744A0e743C,
          0x5F13B17024C14D6D10D5F4c6A04c0877e0C3E50B,

          0x0026dd985dA16f70E9D9cF739b08C6cdF6A7f407,
          0xf4E6232d2f86Bd3399D956b3892d6B1893e1e06E,
          0x59E5fe3a536d2fa69614E0A0692F5C6e3D6dCbfc,
          0x59E5fe3a536d2fa69614E0A0692F5C6e3D6dCbfc,
          0x21E61C2905C0B1d3760984Bb5dC19915557e3Ed4,

          0x21E61C2905C0B1d3760984Bb5dC19915557e3Ed4,
          0x4553448E66559c825eca1E9B6b1A6D8556e8F584,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x51Ca42fb14C727e2E026Df8dab8Be684bfB3ED9A,
          0x6d79af750b839414F63966288e40c8E7fBA3F0CF,
          
          0xE80cc1600Bfc4994f42cdbcBFc783A560e40e81a,
          0xe69DAb397b59f52dC8B65C01533a17538a07E2dD,
          0xe69DAb397b59f52dC8B65C01533a17538a07E2dD,
          0x5bB4c1c7B1149e236cA28A41Ac24528b0B35f9c5,
          0x02bfd87Fb5b3a7a2e643C0aF9a72e88E0F868077,

          0x7d6423C5077B2540Fafd6258CE93314048DBE41c,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0xc030ad321418b559227Bc444d81DFeCF6fdaDC14,
          
          0xa802c75E4B00a239F18d238e7054aE058506d221,
          0x038B2e8c7DbcD053f749edB6c2cDF25CA3C6bEde,
          0x59E5fe3a536d2fa69614E0A0692F5C6e3D6dCbfc,
          0x3214e141bdAe08Be1f382A885cf3d2a448A5E780,
          0xfa16125e504c10DF4d0bd6942d5c3932F76E9918,
          
          0xCc879Ab4DE63FC7Be6aAca522285D6F5d816278e,
          0x8FDe1AC3a0459b4f7b3659c7a1df355F49601176,
          0xA9Ea60Bb786e9F12b29bB44ba73a38418e0A365b,
          0xafe47D6f287cE1397f6D1af56868ce8981D555e8,
          0xFb2EB8734F57bDEa9cbF2956972f25c0a704862e,
          
          0xF9542D1D4b7356A774eEb7FDdF7efD92B78bDE51,
          0x0F6b73e16C8cF8BAC1EA69b50e142c2edB8f1c96,
          0x857D5884FC42CEa646bD62Cc84F806aEB9a2AE6F,
          0x8Cc1226540EB7Da70BD230BB4bfaEeBd3Ff053cb,
          0xb96A1688DDCE8da1F2e2aDD33962768224E39E4C,
          
          0xb96A1688DDCE8da1F2e2aDD33962768224E39E4C
      ];

      for (uint256 i = 0; i < existingHolders.length; i++) {
        supply.increment();
        _safeMint(existingHolders[i], supply.current());
      }
  }

  function setMaxMintAmountPerTx(uint256 _maxMintAmountPerTx) public onlyOwner {
    maxMintAmountPerTx = _maxMintAmountPerTx;
  }

  function setHiddenMetadataUri(string memory _hiddenMetadataUri) public onlyOwner {
    hiddenMetadataUri = _hiddenMetadataUri;
  }

  function setUriPrefix(string memory _uriPrefix) public onlyOwner {
    uriPrefix = _uriPrefix;
  }

  function setUriSuffix(string memory _uriSuffix) public onlyOwner {
    uriSuffix = _uriSuffix;
  }

  function setPublicMintEnabled(bool _state) public onlyOwner {
    publicMintEnabled = _state;
  }

  function setWhitelistMintEnabled(bool _state) public onlyOwner {
    whitelistMintEnabled = _state;
  }

  function withdraw() public onlyOwner {
    (bool os, ) = payable(owner()).call{value: address(this).balance}("");
    require(os);
  }

  function _mintLoop(address _receiver, uint256 _mintAmount) internal {
    for (uint256 i = 0; i < _mintAmount; i++) {
      supply.increment();
      _safeMint(_receiver, supply.current());
    }
  }

  function _baseURI() internal view virtual override returns (string memory) {
    return uriPrefix;
  }
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"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":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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","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":"_address","type":"address"}],"name":"hasPublicMinted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"hasWhitelistMinted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hiddenMetadataUri","outputs":[{"internalType":"string","name":"","type":"string"}],"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":"address","name":"_address","type":"address"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMintAmountPerTx","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMintAmountPerWhitelistTx","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_mintAmount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_mintAmount","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"mintForAddress","outputs":[],"stateMutability":"nonpayable","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":[],"name":"publicMintEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revealed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","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":"uint256","name":"_cost","type":"uint256"}],"name":"setCost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_hiddenMetadataUri","type":"string"}],"name":"setHiddenMetadataUri","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxMintAmountPerTx","type":"uint256"}],"name":"setMaxMintAmountPerTx","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_state","type":"bool"}],"name":"setPublicMintEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_state","type":"bool"}],"name":"setRevealed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uriPrefix","type":"string"}],"name":"setUriPrefix","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uriSuffix","type":"string"}],"name":"setUriSuffix","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_state","type":"bool"}],"name":"setWhitelistMintEnabled","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uriPrefix","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uriSuffix","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"walletOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"whitelistAdd","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"users","type":"address[]"}],"name":"whitelistAddMultiple","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_mintAmount","type":"uint256"}],"name":"whitelistMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"whitelistMintEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"whitelistRemove","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]