contract ERC721Opt is ERC721, ReentrancyGuard {

  string private constant ERR = "ERC721Base: 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;

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

  /**
   * @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(msg.sender == _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 {
    uint256 tidEnd = _currentTokenId + numMint;
    
    uint 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(msg.sender).transfer(dust);
    }
  }

  /**
   * @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 number of minted token
   */
  function totalSupply() external view returns (uint256) {
    return _currentTokenId;
  }

  /**
   * @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 we don't allow ether receive()
   */
  receive() external payable {
    revert(ERR);
  }
}
/**
 * @dev Implementation of Opt ERC721 contract
 */
function withdraw(address account) external onlyOwner {
    uint256 amount = address(this).balance;
    payable(account).transfer(amount);
    emit Withdraw(account, amount);
  }
/**
 * @dev Withdraw rewards
 */