contract ERC721Base is ERC721, ReentrancyGuard {
  // 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;

  // ETH price of one tokenIds
  uint256 private _tokenPrice;

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

  string private constant ERR = "ERC721Base";

  // Fired when funds are distributed
  event Distributed(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 maxPerTx_,
    uint256 price_) external
  {
    require(msg.sender == _initializer, ERR);

    _transferOwnership(owner_);

    ERC721._initialize(name_, symbol_);
    _cap = cap_;
    _maxTxMint = maxPerTx_;
    _tokenPrice = price_;
    // Mint our first token
    _mint(owner_, 0);
    _currentTokenId = 1;
  }

  /**
   * @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 Distribute mint
   */
  function mint(address to, uint256 numMint) external payable nonReentrant {
    require(mintPaused == 0 &&
      numMint > 0 &&
      numMint <= _maxTxMint &&
      _currentTokenId + numMint <= _cap &&
      msg.value >= numMint * _tokenPrice, ERR);

    for (uint256 i = 0; i < numMint; ++i)
      _mint(to, _currentTokenId + i);
    _currentTokenId += numMint;

    uint256 dust = msg.value - (numMint * _tokenPrice);
    if (dust > 0) payable(msg.sender).transfer(dust);
  }

  /**
   * @dev Distribute rewards
   */
  function distribute(
    address[] calldata accounts,
    uint256[] calldata refunds,
    uint256[] calldata percents
  ) external onlyOwner {
    require(
      (refunds.length == 0 || refunds.length == accounts.length) &&
        (percents.length == 0 || percents.length == accounts.length),
      ERR
    );

    uint256 availableAmount = address(this).balance;
    uint256[] memory amounts = new uint256[](accounts.length);

    for (uint256 i = 0; i < refunds.length; ++i) {
      require(refunds[i] <= availableAmount, ERR);
      amounts[i] = refunds[i];
      availableAmount -= refunds[i];
    }

    uint256 amountToShare = availableAmount;
    for (uint256 i = 0; i < percents.length; ++i) {
      uint256 amount = (amountToShare * percents[i]) / 100;
      amounts[i] += (amount <= availableAmount) ? amount : availableAmount;
      availableAmount -= amount;
    }

    for (uint256 i = 0; i < accounts.length; ++i) {
      if (amounts[i] > 0) {
        payable(accounts[i]).transfer(amounts[i]);
        emit Distributed(accounts[i], amounts[i]);
      }
    }
  }

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

  /**
   * @dev we don't allow ether receive()
   */
  receive() external payable {
    revert('Not allowed');
  }
}
/**
 * @dev Implementation of Base ERC721 contract
 */
/**
 * @dev we don't allow ether receive()
 */