// File: Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: ERC2981Base.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

import "./IERC2981.sol";
import "./IERC165.sol";

abstract contract ERC2981Base is IERC165, IERC2981 {
	// Errors
	error IERC2981_INVALID_ROYALTIES();

	// Royalty rate is stored out of 10,000 instead of a percentage to allow for
	// up to two digits below the unit such as 2.5% or 1.25%.
	uint256 private constant ROYALTY_BASE = 10000;

	// Represents the percentage of royalties on each sale on secondary markets.
	// Set to 0 to have no royalties.
	uint256 private _royaltyRate;

	// Address of the recipient of the royalties.
	address private _recipient;

	function _initERC2981Base( address recipient_, uint256 royaltyRate_ ) internal {
		_setRoyaltyInfo( recipient_, royaltyRate_ );
	}

	/**
	* @dev See {IERC2981-royaltyInfo}.
	* 
	* Note: This function should be overriden to revert on a query for non existent token.
	*/
	function royaltyInfo( uint256 tokenId_, uint256 salePrice_ ) public view virtual override returns ( address, uint256 ) {
		if ( salePrice_ == 0 || _royaltyRate == 0 ) {
			return ( _recipient, 0 );
		}
		uint256 _royaltyAmount_ = _royaltyRate * salePrice_ / ROYALTY_BASE;
		return ( _recipient, _royaltyAmount_ );
	}

	/**
	* @dev Sets the royalty rate to `royaltyRate_` and the royalty recipient to `recipient_`.
	* 
	* Requirements: 
	* 
	* - `royaltyRate_` cannot be higher than `ROYALTY_BASE`;
	*/
	function _setRoyaltyInfo( address recipient_, uint256 royaltyRate_ ) internal virtual {
		if ( royaltyRate_ > ROYALTY_BASE ) {
			revert IERC2981_INVALID_ROYALTIES();
		}
		_royaltyRate = royaltyRate_;
		_recipient = recipient_;
	}

	/**
	* @dev See {IERC165-supportsInterface}.
	*/
	function supportsInterface( bytes4 interfaceId_ ) public view virtual override returns ( bool ) {
		return 
			interfaceId_ == type( IERC2981 ).interfaceId ||
			interfaceId_ == type( IERC165 ).interfaceId;
	}
}

// File: ERC721Batch.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

import "./IERC721Metadata.sol";
import "./IERC721Receiver.sol";
import "./Context.sol";

/**
* @dev Required interface of an ERC721 compliant contract.
*/
abstract contract ERC721Batch is Context, IERC721Metadata {
  /**
  * @dev See EIP2309 https://eips.ethereum.org/EIPS/eip-2309
  */
  event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed fromAddress, address indexed toAddress);

  // Errors
  error IERC721_APPROVE_OWNER();
  error IERC721_APPROVE_CALLER();
  error IERC721_CALLER_NOT_APPROVED();
  error IERC721_NONEXISTANT_TOKEN();
  error IERC721_NULL_ADDRESS_BALANCE();
  error IERC721_NULL_ADDRESS_TRANSFER();
  error IERC721_NON_ERC721_RECEIVER();

  // Token name
  string private _name;

  // Token symbol
  string private _symbol;

  // Token Base URI
  string private _baseURI;

  // Token IDs
  uint256 private _numTokens;

  // List of owner addresses
  mapping( uint256 => address ) private _owners;

  // Mapping from token ID to approved address
  mapping( uint256 => address ) private _tokenApprovals;

  // Mapping from owner to operator approvals
  mapping( address => mapping( address => bool ) ) private _operatorApprovals;

  /**
  * @dev Ensures the token exist. 
  * A token exists if it has been minted and is not owned by the null address.
  * 
  * @param tokenId_ uint256 ID of the token to verify
  */
  modifier exists( uint256 tokenId_ ) {
    if ( ! _exists( tokenId_ ) ) {
      revert IERC721_NONEXISTANT_TOKEN();
    }
    _;
  }

  /**
  * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
  */
  function _initERC721BatchMetadata( string memory name_, string memory symbol_ ) internal {
    _name   = name_;
    _symbol = symbol_;
  }

  /**
  * @dev Mints `qty_` tokens into `to_`.
  * 
  * This internal function can be used to perform token minting.
  * 
  * Emits a {ConsecutiveTransfer} event.
  */
  function _mint( address to_, uint256 qty_ ) internal virtual {
    _owners[ _numTokens ] = to_;
    uint256 _lastToken_ = _numTokens + qty_ - 1;
    if ( _lastToken_ != _numTokens ) {
      _owners[ _lastToken_ ] = to_;
    }
    emit ConsecutiveTransfer( _numTokens, _lastToken_, address( 0 ), to_ );
    _numTokens = _lastToken_ + 1;
  }

  /**
  * @dev Internal function used to set the base URI of the collection.
  */
  function _setBaseURI( string memory baseURI_ ) internal virtual {
    _baseURI = baseURI_;
  }

  /**
  * @dev Transfers `tokenId_` from `from_` to `to_`.
  *
  * This internal function can be used to implement alternative mechanisms to perform 
  * token transfer, such as signature-based, or token burning.
  * 
  * Emits a {Transfer} event.
  */
  function _transfer( address from_, address to_, uint256 tokenId_ ) internal virtual {
    _tokenApprovals[ tokenId_ ] = address( 0 );
    uint256 _previousId_ = tokenId_ - 1;
    uint256 _nextId_     = tokenId_ + 1;
    bool _previousShouldUpdate_ = _exists( _previousId_ ) &&
                                  _owners[ _previousId_ ] == address( 0 );
    bool _nextShouldUpdate_ = _exists( _nextId_ ) &&
                              _owners[ _nextId_ ] == address( 0 );

    if ( _previousShouldUpdate_ ) {
      _owners[ _previousId_ ] = from_;
    }

    if ( _nextShouldUpdate_ ) {
      _owners[ _nextId_ ] = from_;
    }

    _owners[ tokenId_ ] = to_;

    emit Transfer( from_, to_, tokenId_ );
  }

  /**
  * @dev See {IERC721-approve}.
  */
  function approve( address to_, uint256 tokenId_ ) external virtual exists( tokenId_ ) {
    address _operator_ = _msgSender();
    address _tokenOwner_ = _ownerOf( tokenId_ );
    bool _isApproved_ = _isApprovedOrOwner( _tokenOwner_, _operator_, tokenId_ );

    if ( ! _isApproved_ ) {
      revert IERC721_CALLER_NOT_APPROVED();
    }

    if ( to_ == _tokenOwner_ ) {
      revert IERC721_APPROVE_OWNER();
    }

    _tokenApprovals[ tokenId_ ] = to_;
    emit Approval( _tokenOwner_, to_, tokenId_ );
  }

  /**
  * @dev See {IERC721-safeTransferFrom}.
  * 
  * Note: We can ignore `from_` as we can compare everything to the actual token owner, 
  * but we cannot remove this parameter to stay in conformity with IERC721
  */
  function safeTransferFrom( address from_, address to_, uint256 tokenId_ ) external virtual exists( tokenId_ ) {
    address _operator_ = _msgSender();
    address _tokenOwner_ = _ownerOf( tokenId_ );
    bool _isApproved_ = _isApprovedOrOwner( _tokenOwner_, _operator_, tokenId_ );

    if ( ! _isApproved_ ) {
      revert IERC721_CALLER_NOT_APPROVED();
    }

    if ( to_ == address( 0 ) ) {
      revert IERC721_NULL_ADDRESS_TRANSFER();
    }

    _transfer( _tokenOwner_, to_, tokenId_ );

    if ( ! _checkOnERC721Received( _tokenOwner_, to_, tokenId_, "" ) ) {
      revert IERC721_NON_ERC721_RECEIVER();
    }
  }

  /**
  * @dev See {IERC721-safeTransferFrom}.
  * 
  * Note: We can ignore `from_` as we can compare everything to the actual token owner, 
  * but we cannot remove this parameter to stay in conformity with IERC721
  */
  function safeTransferFrom( address from_, address to_, uint256 tokenId_, bytes calldata data_ ) external virtual exists( tokenId_ ) {
    address _operator_ = _msgSender();
    address _tokenOwner_ = _ownerOf( tokenId_ );
    bool _isApproved_ = _isApprovedOrOwner( _tokenOwner_, _operator_, tokenId_ );

    if ( ! _isApproved_ ) {
      revert IERC721_CALLER_NOT_APPROVED();
    }

    if ( to_ == address( 0 ) ) {
      revert IERC721_NULL_ADDRESS_TRANSFER();
    }

    _transfer( _tokenOwner_, to_, tokenId_ );

    if ( ! _checkOnERC721Received( _tokenOwner_, to_, tokenId_, data_ ) ) {
      revert IERC721_NON_ERC721_RECEIVER();
    }
  }

  /**
  * @dev See {IERC721-setApprovalForAll}.
  */
  function setApprovalForAll( address operator_, bool approved_ ) public virtual override {
    address _account_ = _msgSender();
    if ( operator_ == _account_ ) {
      revert IERC721_APPROVE_CALLER();
    }

    _operatorApprovals[ _account_ ][ operator_ ] = approved_;
    emit ApprovalForAll( _account_, operator_, approved_ );
  }

  /**
  * @dev See {IERC721-transferFrom}.
  * 
  * Note: We can ignore `from_` as we can compare everything to the actual token owner, 
  * but we cannot remove this parameter to stay in conformity with IERC721
  */
  function transferFrom( address from_, address to_, uint256 tokenId_ ) external virtual exists( tokenId_ ) {
    address _operator_ = _msgSender();
    address _tokenOwner_ = _ownerOf( tokenId_ );
    bool _isApproved_ = _isApprovedOrOwner( _tokenOwner_, _operator_, tokenId_ );

    if ( ! _isApproved_ ) {
      revert IERC721_CALLER_NOT_APPROVED();
    }

    if ( to_ == address( 0 ) ) {
      revert IERC721_NULL_ADDRESS_TRANSFER();
    }

    _transfer( _tokenOwner_, to_, tokenId_ );
  }

  /**
  * @dev Internal function returning the number of tokens in `tokenOwner_`'s account.
  */
  function _balanceOf( address tokenOwner_ ) internal view virtual returns ( uint256 balance ) {
    if ( tokenOwner_ == address( 0 ) ) {
      return 0;
    }

    uint256 _supplyMinted_ = _supplyMinted();
    uint256 _count_ = 0;
    address _currentTokenOwner_;
    for ( uint256 i; i < _supplyMinted_; i++ ) {
      if ( _exists( i ) ) {
        if ( _owners[ i ] != address( 0 ) ) {
          _currentTokenOwner_ = _owners[ i ];
        }
        if ( tokenOwner_ == _currentTokenOwner_ ) {
          _count_++;
        }
      }
    }
    return _count_;
  }

  /**
  * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
  * The call is not executed if the target address is not a contract.
  *
  * @param from_ address representing the previous owner of the given token ID
  * @param to_ target address that will receive the tokens
  * @param tokenId_ uint256 ID of the token to be transferred
  * @param data_ bytes optional data to send along with the call
  * @return bool whether the call correctly returned the expected magic value
  */
  function _checkOnERC721Received( address from_, address to_, uint256 tokenId_, bytes memory data_ ) internal virtual returns ( bool ) {
    // This method relies on extcodesize, which returns 0 for contracts in
    // construction, since the code is only stored at the end of the
    // constructor execution.
    // 
    // IMPORTANT
    // It is unsafe to assume that an address not flagged by this method
    // is an externally-owned account (EOA) and not a contract.
    //
    // Among others, the following types of addresses will not be flagged:
    //
    //  - an externally-owned account
    //  - a contract in construction
    //  - an address where a contract will be created
    //  - an address where a contract lived, but was destroyed
    uint256 _size_;
    assembly {
      _size_ := extcodesize( to_ )
    }

    // If address is a contract, check that it is aware of how to handle ERC721 tokens
    if ( _size_ > 0 ) {
      try IERC721Receiver( to_ ).onERC721Received( _msgSender(), from_, tokenId_, data_ ) returns ( bytes4 retval ) {
        return retval == IERC721Receiver.onERC721Received.selector;
      }
      catch ( bytes memory reason ) {
        if ( reason.length == 0 ) {
          revert IERC721_NON_ERC721_RECEIVER();
        }
        else {
          assembly {
            revert( add( 32, reason ), mload( reason ) )
          }
        }
      }
    }
    else {
      return true;
    }
  }

  /**
  * @dev Internal function returning whether a token exists. 
  * A token exists if it has been minted and is not owned by the null address.
  * 
  * @param tokenId_ uint256 ID of the token to verify
  * 
  * @return bool whether the token exists
  */
  function _exists( uint256 tokenId_ ) internal view virtual returns ( bool ) {
    return tokenId_ < _numTokens;
  }

  /**
  * @dev Internal function returning whether `operator_` is allowed 
  * to manage tokens on behalf of `tokenOwner_`.
  * 
  * @param tokenOwner_ address that owns tokens
  * @param operator_ address that tries to manage tokens
  * 
  * @return bool whether `operator_` is allowed to handle the token
  */
  function _isApprovedForAll( address tokenOwner_, address operator_ ) internal view virtual returns ( bool ) {
    return _operatorApprovals[ tokenOwner_ ][ operator_ ];
  }

  /**
  * @dev Internal function returning whether `operator_` is allowed to handle `tokenId_`
  * 
  * Note: To avoid multiple checks for the same data, it is assumed that existence of `tokeId_` 
  * has been verified prior via {_exists}
  * If it hasn't been verified, this function might panic
  * 
  * @param operator_ address that tries to handle the token
  * @param tokenId_ uint256 ID of the token to be handled
  * 
  * @return bool whether `operator_` is allowed to handle the token
  */
  function _isApprovedOrOwner( address tokenOwner_, address operator_, uint256 tokenId_ ) internal view virtual returns ( bool ) {
    bool _isApproved_ = operator_ == tokenOwner_ ||
                        operator_ == _tokenApprovals[ tokenId_ ] ||
                        _isApprovedForAll( tokenOwner_, operator_ );
    return _isApproved_;
  }

  /**
  * @dev Internal function returning the owner of the `tokenId_` token.
  * 
  * @param tokenId_ uint256 ID of the token to verify
  * 
  * @return address the address of the token owner
  */
  function _ownerOf( uint256 tokenId_ ) internal view virtual returns ( address ) {
    uint256 _tokenId_ = tokenId_;
    address _tokenOwner_ = _owners[ tokenId_ ];
    while ( _tokenOwner_ == address( 0 ) ) {
      _tokenId_ --;
      _tokenOwner_ = _owners[ _tokenId_ ];
    }

    return _tokenOwner_;
  }

  /**
  * @dev Internal function returning the total number of tokens minted
  * 
  * @return uint256 the number of tokens that have been minted so far
  */
  function _supplyMinted() internal view virtual returns ( uint256 ) {
    return _numTokens;
  }

  /**
  * @dev Converts a `uint256` to its ASCII `string` decimal representation.
  */
  function _toString( uint256 value ) internal pure returns ( string memory ) {
    // Inspired by OraclizeAPI's implementation - MIT licence
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
    if ( value == 0 ) {
      return "0";
    }
    uint256 temp = value;
    uint256 digits;
    while ( temp != 0 ) {
      digits ++;
      temp /= 10;
    }
    bytes memory buffer = new bytes( digits );
    while ( value != 0 ) {
      digits -= 1;
      buffer[ digits ] = bytes1( uint8( 48 + uint256( value % 10 ) ) );
      value /= 10;
    }
    return string( buffer );
  }

  /**
  * @dev Returns the number of tokens in `tokenOwner_`'s account.
  */
  function balanceOf( address tokenOwner_ ) external view virtual returns ( uint256 balance ) {
    return _balanceOf( tokenOwner_ );
  }

  /**
  * @dev Returns the account approved for `tokenId_` token.
  *
  * Requirements:
  *
  * - `tokenId_` must exist.
  */
  function getApproved( uint256 tokenId_ ) external view virtual exists( tokenId_ ) returns ( address operator ) {
    return _tokenApprovals[ tokenId_ ];
  }

  /**
  * @dev Returns if the `operator_` is allowed to manage all of the assets of `tokenOwner_`.
  *
  * See {setApprovalForAll}
  */
  function isApprovedForAll( address tokenOwner_, address operator_ ) external view virtual returns ( bool ) {
    return _isApprovedForAll( tokenOwner_, operator_ );
  }

  /**
  * @dev See {IERC721Metadata-name}.
  */
  function name() public view virtual override returns ( string memory ) {
    return _name;
  }

  /**
  * @dev Returns the owner of the `tokenId_` token.
  *
  * Requirements:
  *
  * - `tokenId_` must exist.
  */
  function ownerOf( uint256 tokenId_ ) external view virtual exists( tokenId_ ) returns ( address owner ) {
    return _ownerOf( tokenId_ );
  }

  /**
  * @dev See {IERC165-supportsInterface}.
  */
  function supportsInterface( bytes4 interfaceId_ ) public view virtual override returns ( bool ) {
    return 
      interfaceId_ == type( IERC721Metadata ).interfaceId ||
      interfaceId_ == type( IERC721 ).interfaceId ||
      interfaceId_ == type( IERC165 ).interfaceId;
  }

  /**
  * @dev See {IERC721Metadata-symbol}.
  */
  function symbol() public view virtual override returns ( string memory ) {
    return _symbol;
  }

  /**
  * @dev See {IERC721Metadata-tokenURI}.
  */
  function tokenURI( uint256 tokenId_ ) public view virtual override exists( tokenId_ ) returns ( string memory ) {
    return bytes( _baseURI ).length > 0 ? string( abi.encodePacked( _baseURI, _toString( tokenId_ ) ) ) : _toString( tokenId_ );
  }
}

// File: ERC721BatchBurnable.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

import "./ERC721Batch.sol";

abstract contract ERC721BatchBurnable is ERC721Batch {
  // List of burned tokens
  mapping( uint256 => bool ) private _burned;

	/**
	* @dev Burns `tokenId_`.
	*
	* Requirements:
	*
	* - `tokenId_` must exist
	* - The caller must own `tokenId_` or be an approved operator
	*/
	function burn( uint256 tokenId_ ) external exists( tokenId_ ) {
    address _operator_ = _msgSender();
    address _tokenOwner_ = _ownerOf( tokenId_ );
    bool _isApproved_ = _isApprovedOrOwner( _tokenOwner_, _operator_, tokenId_ );

    if ( ! _isApproved_ ) {
      revert IERC721_CALLER_NOT_APPROVED();
    }

    _burned[ tokenId_ ] = true;
		_transfer( _tokenOwner_, address( 0 ), tokenId_ );
	}

  /**
  * @dev Internal function returning whether a token exists. 
  * A token exists if it has been minted and is not owned by the null address.
  * 
  * @param tokenId_ uint256 ID of the token to verify
  * 
  * @return bool whether the token exists
  */
  function _exists( uint256 tokenId_ ) internal override view returns ( bool ) {
    return ! _burned[ tokenId_ ] && super._exists( tokenId_ );
  }
}

// File: IAdministrable.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;
import './IOwnable.sol';

/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract IAdministrable is IOwnable {
	// Errors
	error IAdministrable_NOT_ADMIN();

	bytes32 public constant ADMIN_ROLE = keccak256( "ADMIN_ROLE" );

	// The list of admin addresses
	mapping( address => bool ) private _admin;

	/**
	* @dev Emitted when admin role is granted.
	*/
	event RoleGranted( bytes32 indexed role, address indexed account, address indexed operator );

	/**
	* @dev Emitted when admin role is revoked or renounced.
	*/
	event RoleRevoked( bytes32 indexed role, address indexed account, address indexed operator );

	/**
	* @dev Throws if called by any account that is not an admin.
	*/
	modifier onlyAdmin() {
		if ( ! isAdmin( msg.sender ) ) {
			revert IAdministrable_NOT_ADMIN();
		}
		_;
	}

	/**
	* @dev Grants admin privileges to `account_`.
	* Can only be called by the current owner.
	*/
	function grantAdmin( address account_ ) public virtual onlyOwner {
		_admin[ account_ ] = true;
		emit RoleGranted( ADMIN_ROLE, account_, msg.sender );
	}

	/**
	* @dev Renonce admin privileges.
	* Can only be called by the current owner.
	*/
	function renounceAdmin() public virtual onlyAdmin {
		delete _admin[ msg.sender ];
		emit RoleRevoked( ADMIN_ROLE, msg.sender, msg.sender );
	}

	/**
	* @dev Revokes admin privileges from `account_`.
	* Can only be called by the current owner.
	*/
	function revokeAdmin( address account_ ) public virtual onlyOwner {
		delete _admin[ account_ ];
		emit RoleRevoked( ADMIN_ROLE, account_, msg.sender );
	}

	/**
	* @dev Returns whether the address is an admin or not.
	* The contract owner is always considered an admin.
	*/
	function isAdmin( address account_ ) public view virtual returns ( bool ) {
		return account_ == owner() || _admin[ account_ ];
	}
}

// File: IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity 0.8.10;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: IERC2981.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

interface IERC2981 {
  /**
  * @dev ERC165 bytes to add to interface array - set in parent contract
  * implementing this standard
  *
  * bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
  * bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
  * _registerInterface(_INTERFACE_ID_ERC2981);
  *
  * @notice Called with the sale price to determine how much royalty
  *           is owed and to whom.
  * @param _tokenId - the NFT asset queried for royalty information
  * @param _salePrice - the sale price of the NFT asset specified by _tokenId
  * @return receiver - address of who should be sent the royalty payment
  * @return royaltyAmount - the royalty payment amount for _salePrice
  */
  function royaltyInfo(uint256 _tokenId, uint256 _salePrice) external view returns (address receiver, uint256 royaltyAmount);
}

// File: IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)

pragma solidity 0.8.10;

import "./IERC165.sol";

/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
  /**
  * @dev Emitted when `tokenId_` token is transferred from `from_` to `to_`.
  */
  event Transfer( address indexed from_, address indexed to_, uint256 indexed tokenId_ );

  /**
  * @dev Emitted when `owner_` enables `approved_` to manage the `tokenId_` token.
  */
  event Approval( address indexed owner_, address indexed approved_, uint256 indexed tokenId_ );

  /**
  * @dev Emitted when `owner_` enables or disables (`approved`) `operator_` to manage all of its assets.
  */
  event ApprovalForAll( address indexed owner_ , address indexed operator_ , bool approved_ );

  /**
  * @dev Gives permission to `to_` to transfer `tokenId_` token to another account.
  * The approval is cleared when the token is transferred.
  *
  * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
  *
  * Requirements:
  *
  * - The caller must own the token or be an approved operator.
  * - `tokenId_` must exist.
  *
  * Emits an {Approval} event.
  */
  function approve( address to_, uint256 tokenId_ ) external;

  /**
  * @dev Safely transfers `tokenId_` token from `from_` to `to_`, checking first that contract recipients
  * are aware of the ERC721 protocol to prevent tokens from being forever locked.
  *
  * Requirements:
  *
  * - `from_` cannot be the zero address.
  * - `to_` cannot be the zero address.
  * - `tokenId_` token must exist and be owned by `from_`.
  * - If the caller is not `from_`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
  * - If `to_` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
  *
  * Emits a {Transfer} event.
  */
  function safeTransferFrom( address from_, address to_, uint256 tokenI_d ) external;

  /**
  * @dev Safely transfers `tokenId_` token from `from_` to `to_`.
  *
  * Requirements:
  *
  * - `from_` cannot be the zero address.
  * - `to_` cannot be the zero address.
  * - `tokenId_` token must exist and be owned by `from_`.
  * - If the caller is not `from_`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
  * - If `to_` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
  *
  * Emits a {Transfer} event.
  */
  function safeTransferFrom( address from_, address to_, uint256 tokenId_, bytes calldata data_ ) external;

  /**
  * @dev Approve or remove `operator_` as an operator for the caller.
  * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
  *
  * Requirements:
  *
  * - The `operator_` cannot be the caller.
  *
  * Emits an {ApprovalForAll} event.
  */
  function setApprovalForAll( address operator_, bool approved_ ) external;

  /**
  * @dev Transfers `tokenId_` token from `from_` to `to_`.
  *
  * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
  *
  * Requirements:
  *
  * - `from_` cannot be the zero address.
  * - `to_` cannot be the zero address.
  * - `tokenId_` token must be owned by `from_`.
  * - If the caller is not `from_`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
  *
  * Emits a {Transfer} event.
  */
  function transferFrom( address from_, address to_, uint256 tokenId_ ) external;

  /**
  * @dev Returns the number of tokens in `tokenOwner_`'s account.
  */
  function balanceOf( address tokenOwner_ ) external view returns ( uint256 balance );

  /**
  * @dev Returns the account approved for `tokenId_` token.
  *
  * Requirements:
  *
  * - `tokenId_` must exist.
  */
  function getApproved( uint256 tokenId_ ) external view returns ( address operator );

  /**
  * @dev Returns if the `operator_` is allowed to manage all of the assets of `tokenOwner_`.
  *
  * See {setApprovalForAll}
  */
  function isApprovedForAll( address tokenOwner_, address operator_ ) external view returns ( bool );

  /**
  * @dev Returns the owner of the `tokenId_` token.
  *
  * Requirements:
  *
  * - `tokenId_` must exist.
  */
  function ownerOf( uint256 tokenId_ ) external view returns ( address owner );
}

// File: IERC721Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity 0.8.10;

import "./IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

// File: IERC721Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721Receiver.sol)

pragma solidity 0.8.10;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: IOwnable.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract IOwnable {
	// Errors
	error IOwnable_NOT_OWNER();

	// The owner of the contract
	address private _owner;

	/**
	* @dev Emitted when contract ownership changes.
	*/
	event OwnershipTransferred( address indexed previousOwner, address indexed newOwner );

	/**
	* @dev Initializes the contract setting the deployer as the initial owner.
	*/
	function _initIOwnable( address owner_ ) internal {
		_owner = owner_;
	}

	/**
	* @dev Returns the address of the current owner.
	*/
	function owner() public view virtual returns ( address ) {
		return _owner;
	}

	/**
	* @dev Throws if called by any account other than the owner.
	*/
	modifier onlyOwner() {
		if ( owner() != msg.sender ) {
			revert IOwnable_NOT_OWNER();
		}
		_;
	}

	/**
	* @dev Transfers ownership of the contract to a new account (`newOwner`).
	* Can only be called by the current owner.
	*/
	function transferOwnership( address newOwner_ ) public virtual onlyOwner {
		address _oldOwner_ = _owner;
		_owner = newOwner_;
		emit OwnershipTransferred( _oldOwner_, newOwner_ );
	}
}

// File: IPausable.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

abstract contract IPausable {
	// Errors
	error IPausable_SALE_NOT_CLOSED();
	error IPausable_SALE_NOT_OPEN();
	error IPausable_PRESALE_NOT_OPEN();

	// Enum to represent the sale state, defaults to ``CLOSED``.
	enum SaleState { CLOSED, PRESALE, SALE }

	// The current state of the contract
	SaleState public saleState;

	/**
	* @dev Emitted when the sale state changes
	*/
	event SaleStateChanged( SaleState indexed previousState, SaleState indexed newState );

	/**
	* @dev Sale state can have one of 3 values, ``CLOSED``, ``PRESALE``, or ``SALE``.
	*/
	function _setSaleState( SaleState newState_ ) internal virtual {
		SaleState _previousState_ = saleState;
		saleState = newState_;
		emit SaleStateChanged( _previousState_, newState_ );
	}

	/**
	* @dev Throws if sale state is not ``CLOSED``.
	*/
	modifier saleClosed {
		if ( saleState != SaleState.CLOSED ) {
			revert IPausable_SALE_NOT_CLOSED();
		}
		_;
	}

	/**
	* @dev Throws if sale state is not ``SALE``.
	*/
	modifier saleOpen {
		if ( saleState != SaleState.SALE ) {
			revert IPausable_SALE_NOT_OPEN();
		}
		_;
	}

	/**
	* @dev Throws if sale state is not ``PRESALE``.
	*/
	modifier presaleOpen {
		if ( saleState != SaleState.PRESALE ) {
			revert IPausable_PRESALE_NOT_OPEN();
		}
		_;
	}
}

// File: ITradable.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

contract OwnableDelegateProxy {}

contract ProxyRegistry {
	mapping( address => OwnableDelegateProxy ) public proxies;
}

abstract contract ITradable {
	// OpenSea proxy registry address
	address[] internal _proxyRegistries;

	function _setProxyRegistry( address proxyRegistryAddress_ ) internal {
		_proxyRegistries.push( proxyRegistryAddress_ );
	}

	/**
	* @dev Checks if `operator_` is the registered proxy for `tokenOwner_`.
	* 
	* Note: Use this function to allow whitelisting of registered proxy.
	*/
	function _isRegisteredProxy( address tokenOwner_, address operator_ ) internal view returns ( bool ) {
		for ( uint256 i; i < _proxyRegistries.length; i++ ) {
			ProxyRegistry _proxyRegistry_ = ProxyRegistry( _proxyRegistries[ i ] );
			if ( address( _proxyRegistry_.proxies( tokenOwner_ ) ) == operator_ ) {
				return true;
			}
		}
		return false;
	}
}
// File: VF.sol



// SPDX-License-Identifier: MIT

/**
* Author: Lambdalf the White
*/

pragma solidity 0.8.10;

import './ERC721BatchBurnable.sol';
import './ERC2981Base.sol';
import './IAdministrable.sol';
import './IPausable.sol';
import './ITradable.sol';

contract VF is ERC721BatchBurnable, ERC2981Base, IAdministrable, IPausable, ITradable {
	// Events
	event PaymentSent( address indexed from, address indexed to, uint256 indexed amount );

	// Errors
	error VF_MAX_BATCH();
	error VF_MAX_SUPPLY();
	error VF_TRANSFER_FAIL();
	error VF_INCORRECT_PRICE();
	error VF_NO_ETHER_BALANCE();
	error VF_WALLETS_MISMATCH();
	error VF_INSUFFICIENT_KEY_BALANCE();
	error VF_INSUFFICIENT_ETHER_BALANCE();

	// Max supply
	uint public immutable MAX_SUPPLY;

	// Max TX
	uint public immutable MAX_BATCH;

	// Mint Price
	uint public immutable MINT_PRICE;

	// Team wallets
	address private constant COMMUNITY = 0x73DAdB28902A54d197D541E290a9F05Ed4e033e2; // 25%
	address private constant FOUNDERS  = 0x7E5541CEeb1C1B24Ca847F52f9505533F1c557e0; // 40%
	address private constant MCS       = 0x6Bc8c4Ef598218cbcB903efB9b711dA88C8f202b; // 35%

	constructor(
		uint256 royaltyRate_,
		uint256 maxSupply_,
		uint256 maxBatch_,
		uint256 mintPrice_,
		string memory name_,
		string memory symbol_,
		string memory baseURI_
	) {
		_initIOwnable( _msgSender() );
		_initERC2981Base( COMMUNITY, royaltyRate_ );
		_initERC721BatchMetadata( name_, symbol_ );
		_setBaseURI( baseURI_ );
		MAX_SUPPLY   = maxSupply_;
		MAX_BATCH    = maxBatch_;
		MINT_PRICE   = mintPrice_;
	}

	function mintSale( uint256 qty_ ) external payable saleOpen {
		if ( qty_ > MAX_BATCH ) {
			revert VF_MAX_BATCH();
		}

		if ( qty_ + _supplyMinted() > MAX_SUPPLY ) {
			revert VF_MAX_SUPPLY();
		}

		if ( qty_ * MINT_PRICE != msg.value ) {
			revert VF_INCORRECT_PRICE();
		}
		_mint( _msgSender(), qty_ );
	}

	function withdraw() external onlyAdmin {
		uint256 _balance_ = address( this ).balance;
		if ( _balance_ == 0 ) {
			revert VF_NO_ETHER_BALANCE();
		}

		uint256 _communityShare_ = _balance_ * 250 / 1000;
		uint256 _foundersShare_  = _balance_ * 400 / 1000;
		uint256 _mcsShare_       = _balance_ * 350 / 1000;
		_sendValue( payable( COMMUNITY ), _communityShare_ );
		_sendValue( payable( FOUNDERS  ), _foundersShare_  );
		_sendValue( payable( MCS       ), _mcsShare_       );
	}

	function setSaleState( SaleState newState_ ) external onlyAdmin {
		_setSaleState( newState_ );
	}

	function setBaseURI( string memory baseURI_ ) external onlyOwner {
		_setBaseURI( baseURI_ );
	}

	function setProxyRegistry( address proxyRegistryAddress_ ) external onlyOwner {
		_setProxyRegistry( proxyRegistryAddress_ );
	}

	function setRoyaltyInfo( address recipient_, uint256 royaltyRate_ ) external onlyAdmin {
		_setRoyaltyInfo( recipient_, royaltyRate_ );
	}

	/**
	* @dev Replacement for Solidity's `transfer`: sends `amount_` wei to
	* `recipient_`, forwarding all available gas and reverting on errors.
	*
	* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
	* of certain opcodes, possibly making contracts go over the 2300 gas limit
	* imposed by `transfer`, making them unable to receive funds via
	* `transfer`. {sendValue} removes this limitation.
	*
	* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
	*
	* IMPORTANT: because control is transferred to `recipient`, care must be
	* taken to not create reentrancy vulnerabilities. Consider using
	* {ReentrancyGuard} or the
	* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
	*/
	function _sendValue( address payable recipient_, uint256 amount_ ) internal {
		if ( address( this ).balance < amount_ ) {
			revert VF_INSUFFICIENT_ETHER_BALANCE();
		}
		( bool _success_, ) = recipient_.call{ value: amount_ }( "" );
		if ( ! _success_ ) {
			revert VF_TRANSFER_FAIL();
		}
    emit PaymentSent( address( this ), recipient_, amount_ );
	}

	function supportsInterface( bytes4 interfaceId_ ) public view virtual override(ERC721Batch, ERC2981Base) returns ( bool ) {
		return 
			interfaceId_ == type( IERC2981 ).interfaceId ||
			ERC721Batch.supportsInterface( interfaceId_ );
	}

	/**
	* @dev See {IERC2981-royaltyInfo}.
	* 
	* Note: This function should be overriden to revert on a query for non existent token.
	*/
	function royaltyInfo( uint256 tokenId_, uint256 salePrice_ ) public view virtual override exists( tokenId_ ) returns ( address, uint256 ) {
		return super.royaltyInfo( tokenId_, salePrice_ );
	}

  function _isApprovedForAll( address tokenOwner_, address operator_ ) internal view virtual override returns ( bool ) {
    return _isRegisteredProxy( tokenOwner_, operator_ ) ||
    			 super._isApprovedForAll( tokenOwner_, operator_ );
  }
}