contract ERC721Opt is ERC721, ReentrancyGuard {
  string private constant ERR = 'ERC721Opt: Error';

  // OpenSea proxy registry
  address private immutable _osProxyRegistryAddress;
  // Address allowed to initialize contract
  address private immutable _initializer;

  // Max mints per transaction
  uint256 private _maxTxMint;

  // The CAP of mintable tokenIds
  uint256 private _cap;

  // The CAP of free mintable tokenIds
  uint256 private _freeCap;

  // ETH price of one tokenIds
  uint256 private _tokenPrice;

  // TokenId counter, 1 minted in ctor
  uint256 private _currentTokenId;

  // Advertise mints
  uint256 private _advertised;

  // Mint Running
  uint256 public mintRunning;

  // Whitelist minters
  mapping(address=>uint256) private _wlMinted;

  // Fired when funds are distributed
  event Withdraw(address indexed receiver, uint256 amount);


  // Whitelist address check
  modifier onlyValidAddress(
    uint256 _salt,
    bytes32 _r,
    bytes32 _s,
    uint8 _v
  ) {
    bytes32 hash = keccak256(abi.encode(address(this), _salt, _msgSender()));
    bytes32 message = keccak256(
      abi.encodePacked('\x19Ethereum Signed Message:\n32', hash)
    );
    address sig = ecrecover(message, _v, _r, _s);

    require(owner() == sig, ERR);
    _;
  }

  /**
   * @dev Initialization.
   */
  constructor(address initializer_, address osProxyRegistry_) {
    _osProxyRegistryAddress = osProxyRegistry_;
    _initializer = initializer_;
  }

  /**
   * @dev Clone Initialization.
   */
  function initialize(
    address owner_,
    string memory name_,
    string memory symbol_,
    uint256 cap_,
    uint256 freeCap_,
    uint256 maxPerTx_,
    uint256 price_
  ) external {
    require(_msgSender() == _initializer, ERR);

    _transferOwnership(owner_);

    ERC721._initialize(name_, symbol_);
    _cap = cap_;
    _freeCap = freeCap_ + 1;
    _maxTxMint = maxPerTx_;
    _tokenPrice = price_;
    _currentTokenId = 1;

    emit Transfer(address(0), address(0), 0);
  }

  /**
   * @dev See {IERC721-isApprovedForAll}.
   */
  function isApprovedForAll(address owner, address operator)
    public
    view
    virtual
    override
    returns (bool)
  {
    // Whitelist OpenSea proxy contract for easy trading.
    ProxyRegistry proxyRegistry = ProxyRegistry(_osProxyRegistryAddress);
    if (address(proxyRegistry.proxies(owner)) == operator) {
      return true;
    }
    return super.isApprovedForAll(owner, operator);
  }

  /**
   * @dev mint
   */
  function mint(address to, uint256 numMint) external payable nonReentrant {
    require((mintRunning & 2) != 0, ERR);

    uint256 tidEnd = _currentTokenId + numMint;

    uint256 numPayMint = tidEnd > _freeCap ? tidEnd - _freeCap : 0;
    if (numPayMint > numMint) numPayMint = numMint;

    require(
      numMint > 0 &&
        numMint <= _maxTxMint &&
        tidEnd <= _cap - _advertised &&
        msg.value >= numPayMint * _tokenPrice,
      ERR
    );

    _mint(to, _currentTokenId, numMint);
    _currentTokenId += numMint;

    {
      uint256 dust = msg.value - (numPayMint * _tokenPrice);
      if (dust > 0) payable(_msgSender()).transfer(dust);
    }
  }

  /**
   * @dev Whitelist mint a set of tokenIds for caller
   */
  function whitelistMint(
    uint256 numMint,
    bytes32 r,
    bytes32 s,
    uint8 v
  ) external nonReentrant onlyValidAddress(numMint, r, s, v) {
    // Only once per wallet
    require(_currentTokenId + numMint <= _cap - _advertised &&
      (mintRunning & 1) != 0 &&
      _wlMinted[_msgSender()] == 0, ERR);

    _wlMinted[_msgSender()] = 1;
    _mint(_msgSender(), _currentTokenId, numMint);
    _currentTokenId += numMint;
  }

  /**
   * @dev advertise
   */
  function advertise(address[] memory to, uint256[] memory numMints)
    external
    onlyOwner
  {
    require(to.length == numMints.length, ERR);

    uint256 curTokenId = _cap;
    uint256 maxAdvertised = _advertised;
    for (uint256 i = 0; i < to.length; ++i) {
      for (uint256 j = 0; j < numMints[i]; ++j) {
        emit Transfer(address(0), to[i], curTokenId - j);
      }
      if (numMints[i] > maxAdvertised) maxAdvertised = numMints[i];
    }
    if (maxAdvertised > _advertised) _advertised = maxAdvertised;
  }

  /**
   * @dev Withdraw rewards
   */
  function withdraw(address account) external onlyOwner {
    uint256 amount = address(this).balance;
    payable(account).transfer(amount);
    emit Withdraw(account, amount);
  }

  /**
   * @dev return max number token mintable
   */
  function totalSupply() external view returns (uint256) {
    return _cap;
  }

  /**
   * @dev return number of minted token
   */
  function tokenMinted() external view returns (uint256) {
    return _currentTokenId - 1;
  }

  /**
   * @dev return number of remaining free token
   */
  function freeMintLeft() external view returns (uint256) {
    return _currentTokenId < _freeCap ? _freeCap - _currentTokenId : 0;
  }

  /**
   * @dev Set free mintable token cap
   */
  function setFreeTokenCap(uint256 newCap) external onlyOwner {
    _freeCap = newCap + 1;
  }

  /**
   * @dev Set token price
   */
  function setTokenPrice(uint256 newPrice) external onlyOwner {
    _tokenPrice = newPrice;
  }

  /**
   * @dev See Start / Stop minting.
   */
  function setMintRunning(uint256 newMintRunning) external onlyOwner {
    mintRunning = newMintRunning;
  }

  /**
   * @dev Reset whitlelist address
   */
  function resetWhitelist(address whitelister) external onlyOwner {
    delete(_wlMinted[whitelister]);
  }

  /**
   * @dev we don't allow ether receive()
   */
  receive() external payable {
    revert(ERR);
  }
}
/**
 * @dev Implementation of Opt ERC721 contract
 */
function setTokenPrice(uint256 newPrice) external onlyOwner {
  _tokenPrice = newPrice;
}

/**
 * @dev Set token price
 */