// File: Addresses.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

/**
 * Utility library of inline functions on addresses
 */
library Address {
    /**
     * Returns whether the target address is a contract
     * @dev This function will return false if invoked during the constructor of a contract,
     * as the code is not actually created until after the constructor finishes.
     * @param account address to check
     * @return whether the target address is a contract
     */
    function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            codehash := extcodehash(account)
        }
        return (codehash != accountHash && codehash != 0x0);
    }
}

// File: BaseERC721.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

import "./Addresses.sol";
import "./Strings.sol";
import "./ERC721Receiver.sol";
import "./IERC721.sol";

abstract contract ERC721BasicToken is IERC721 {
    using Address for address;
    using Strings for uint256;

    bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd;
    bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79;
    bytes4 private constant ERC721_RECEIVED = 0xf0b9e5ba;
    bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63;
    bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f;
    bytes4 private constant InterfaceId_ERC165 = 0x01ffc9a7;

    string internal name_;
    string internal symbol_;
    string public baseTokenURI;

    // Array with all token ids, used for enumeration
    uint256[] internal allTokens;

    // Mapping of interface id to whether or not it's supported
    mapping(bytes4 => bool) internal supportedInterfaces;

    // Mapping from owner to list of owned token IDs
    mapping(address => mapping(uint256 => uint256)) internal ownedTokens;

    // Mapping from token ID to index of the owner tokens list
    mapping(uint256 => uint256) internal ownedTokensIndex;

    // Mapping from token id to position in the allTokens array
    mapping(uint256 => uint256) internal allTokensIndex;

    // Mapping from token ID to owner
    mapping(uint256 => address) internal tokenOwner;

    // Mapping from token ID to approved address
    mapping(uint256 => address) internal tokenApprovals;

    // Mapping from owner to number of owned token
    mapping(address => uint256) internal ownedTokensCount;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) internal operatorApprovals;

    /**
     * @dev Guarantees msg.sender is owner of the given token
     * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender
     */
    modifier onlyOwnerOf(uint256 _tokenId) {
        require(ownerOf(_tokenId) == msg.sender, "Only asset owner is allowed");
        _;
    }

    /**
     * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator
     * @param _tokenId uint256 ID of the token to validate
     */
    modifier canTransfer(uint256 _tokenId) {
        require(isApprovedOrOwner(msg.sender, _tokenId), "Can not transfer");
        _;
    }

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _baseTokenUri
    ) {
        name_ = _name;
        symbol_ = _symbol;
        baseTokenURI = _baseTokenUri;

        // register the supported interfaces to confirm to ERC721 via ERC165
        _registerInterface(InterfaceId_ERC721Enumerable);
        _registerInterface(InterfaceId_ERC721Metadata);
        _registerInterface(InterfaceId_ERC721);
        _registerInterface(InterfaceId_ERC721Exists);
        _registerInterface(InterfaceId_ERC165);
    }

    /**
     * @dev Gets the token name
     * @return string representing the token name
     */
    function name() external view returns (string memory) {
        return name_;
    }

    /**
     * @dev Gets the token symbol
     * @return string representing the token symbol
     */
    function symbol() external view returns (string memory) {
        return symbol_;
    }

    /**
     * @dev Returns an URI for a given token ID
     * Throws if the token ID does not exist. May return an empty string.
     * @param _tokenId uint256 ID of the token to query
     */
    function tokenURI(uint256 _tokenId)
        public
        view
        virtual
        returns (string memory)
    {
        require(exists(_tokenId), "Asset does not exist");
        return string(abi.encodePacked(baseTokenURI, _tokenId.toString()));
    }

    /**
     * @dev Gets the balance of the specified address
     * @param _owner address to query the balance of
     * @return uint256 representing the amount owned by the passed address
     */
    function balanceOf(address _owner) public view returns (uint256) {
        require(_owner != address(0), "Zero address not allowed");
        return ownedTokensCount[_owner];
    }

    /**
     * @dev Gets the owner of the specified token ID
     * @param _tokenId uint256 ID of the token to query the owner of
     * @return owner address currently marked as the owner of the given token ID
     */
    function ownerOf(uint256 _tokenId) public view returns (address) {
        address owner = tokenOwner[_tokenId];
        require(owner != address(0), "Token does not exists");
        return owner;
    }

    /**
     * @dev Returns whether the specified token exists
     * @param _tokenId uint256 ID of the token to query the existence of
     * @return whether the token exists
     */
    function exists(uint256 _tokenId) public view returns (bool) {
        address owner = tokenOwner[_tokenId];
        return owner != address(0);
    }

    /**
     * @dev Approves another address to transfer the given token ID
     * The zero address indicates there is no approved address.
     * There can only be one approved address per token at a given time.
     * Can only be called by the token owner or an approved operator.
     * @param _to address to be approved for the given token ID
     * @param _tokenId uint256 ID of the token to be approved
     */
    function _approve(address _to, uint256 _tokenId) internal {
        address owner = ownerOf(_tokenId);
        require(_to != owner, "Can not approve to self");
        require(
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "Not allowed to update approvals"
        );

        tokenApprovals[_tokenId] = _to;
        emit Approval(owner, _to, _tokenId);
    }

    /**
     * @dev Gets the approved address for a token ID, or zero if no address set
     * @param _tokenId uint256 ID of the token to query the approval of
     * @return address currently approved for the given token ID
     */
    function getApproved(uint256 _tokenId) public view returns (address) {
        return tokenApprovals[_tokenId];
    }

    /**
     * @dev Sets or unsets the approval of a given operator
     * An operator is allowed to transfer all tokens of the sender on their behalf
     * @param _to operator address to set the approval
     * @param _approved representing the status of the approval to be set
     */
    function _setApprovalForAll(address _to, bool _approved) internal {
        require(_to != msg.sender, "Can not approve to self");
        operatorApprovals[msg.sender][_to] = _approved;
        emit ApprovalForAll(msg.sender, _to, _approved);
    }

    /**
     * @dev Tells whether an operator is approved by a given owner
     * @param _owner owner address which you want to query the approval of
     * @param _operator operator address which you want to query the approval of
     * @return bool whether the given operator is approved by the given owner
     */
    function isApprovedForAll(address _owner, address _operator)
        public
        view
        returns (bool)
    {
        return operatorApprovals[_owner][_operator];
    }

    /**
     * @dev Transfers the ownership of a given token ID to another address
     * Usage of this method is discouraged, use `safeTransferFrom` whenever possible
     * Requires the msg sender to be the owner, approved, or operator
     * @param _from current owner of the token
     * @param _to address to receive the ownership of the given token ID
     * @param _tokenId uint256 ID of the token to be transferred
     */
    function _transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) internal canTransfer(_tokenId) {
        _transfer(_from, _to, _tokenId);
    }

    // This function assumes we have verified that _from is allowed to make transaction
    function _transferOnBehalf(
        address _from,
        address _to,
        uint256 _tokenId
    ) internal {
        require(isApprovedOrOwner(_from, _tokenId), "Can not transfer");
        _transfer(_from, _to, _tokenId);
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _tokenId
    ) private {
        require(_to != address(0), "Zero address not allowed");
        clearApproval(_from, _tokenId);
        removeTokenFrom(_from, _tokenId);
        addTokenTo(_to, _tokenId);

        emit Transfer(_from, _to, _tokenId);
    }

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     *
     * Requires the msg sender to be the owner, approved, or operator
     * @param _from current owner of the token
     * @param _to address to receive the ownership of the given token ID
     * @param _tokenId uint256 ID of the token to be transferred
     */
    function _safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) internal {
        // solium-disable-next-line arg-overflow
        _safeTransferFrom(_from, _to, _tokenId, "");
    }

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     * Requires the msg sender to be the owner, approved, or operator
     * @param _from current owner of the token
     * @param _to address to receive the ownership of the given token ID
     * @param _tokenId uint256 ID of the token to be transferred
     * @param _data bytes data to send along with a safe transfer check
     */
    function _safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) internal {
        _transferFrom(_from, _to, _tokenId);
        // solium-disable-next-line arg-overflow
        require(
            checkAndCallSafeTransfer(_from, _to, _tokenId, _data),
            "Safe Transfer failed"
        );
    }

    /**
     * @dev Returns whether the given spender can transfer a given token ID
     * @param _spender address of the spender to query
     * @param _tokenId uint256 ID of the token to be transferred
     * @return bool whether the msg.sender is approved for the given token ID,
     *  is an operator of the owner, or is the owner of the token
     */
    function isApprovedOrOwner(address _spender, uint256 _tokenId)
        internal
        view
        returns (bool)
    {
        address owner = ownerOf(_tokenId);
        // Disable solium check because of
        // https://github.com/duaraghav8/Solium/issues/175
        // solium-disable-next-line operator-whitespace
        return (_spender == owner ||
            getApproved(_tokenId) == _spender ||
            isApprovedForAll(owner, _spender));
    }

    /**
     * @dev Internal function to mint a new token
     * Reverts if the given token ID already exists
     * @param _to The address that will own the minted token
     * @param _tokenId uint256 ID of the token to be minted by the msg.sender
     */
    function _mint(address _to, uint256 _tokenId) internal {
        require(_to != address(0), "Zero address not allowed");
        require(!exists(_tokenId), "Asset already exists");
        addTokenTo(_to, _tokenId);
        allTokensIndex[_tokenId] = allTokens.length;
        allTokens.push(_tokenId);

        emit Transfer(address(0), _to, _tokenId);
    }

    function removeFromAllTokens(uint256 tokenId) private {
        uint256 lastTokenIndex = allTokens.length - 1;
        uint256 tokenIndex = allTokensIndex[tokenId];
        uint256 lastTokenId = allTokens[lastTokenIndex];

        allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete allTokensIndex[tokenId];
        allTokens.pop();
    }

    /**
     * @dev Internal function to burn a specific token
     * Reverts if the token does not exist
     * @param _tokenId uint256 ID of the token being burned by the msg.sender
     */
    function _burn(address _owner, uint256 _tokenId)
        internal
        canTransfer(_tokenId)
    {
        clearApproval(_owner, _tokenId);
        removeTokenFrom(_owner, _tokenId);
        removeFromAllTokens(_tokenId);
        tokenOwner[_tokenId] = address(0);
        emit Transfer(_owner, address(0), _tokenId);
    }

    /**
     * @dev Internal function to clear current approval of a given token ID
     * Reverts if the given address is not indeed the owner of the token
     * @param _owner owner of the token
     * @param _tokenId uint256 ID of the token to be transferred
     */
    function clearApproval(address _owner, uint256 _tokenId) internal {
        require(
            ownerOf(_tokenId) == _owner,
            "Asset does not belong to given owmer"
        );
        if (tokenApprovals[_tokenId] != address(0)) {
            tokenApprovals[_tokenId] = address(0);
            emit Approval(_owner, address(0), _tokenId);
        }
    }

    /**
     * @dev Internal function to add a token ID to the list of a given address
     * @param _to address representing the new owner of the given token ID
     * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function addTokenTo(address _to, uint256 _tokenId) internal {
        uint256 length = balanceOf(_to);
        ownedTokens[_to][length] = _tokenId;
        ownedTokensIndex[_tokenId] = length;
        ownedTokensCount[_to] = ownedTokensCount[_to] + 1;  // increment balance of `_to` by 1
        tokenOwner[_tokenId] = _to; // assign `_to` as owner of token
    }

    /**
     * @dev Internal function to remove a token ID from the list of a given address
     * @param _from address representing the previous owner of the given token ID
     * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function removeTokenFrom(address _from, uint256 _tokenId) internal {
        uint256 lastTokenIndex = ownedTokensCount[_from] - 1;
        uint256 tokenIndex = ownedTokensIndex[_tokenId];

        // when we want to delete last token, the swap operation in unnecessary
        if(lastTokenIndex != tokenIndex) {
            uint256 lastTokenId = ownedTokens[_from][lastTokenIndex];
            ownedTokens[_from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }
        // This also deletes the contents at the last position of the array
        delete ownedTokensIndex[_tokenId];
        delete ownedTokens[_from][lastTokenIndex];
        ownedTokensCount[_from] = ownedTokensCount[_from]-1; // decrease balance of _from by 1
    }

    /**
     * @dev Internal function to invoke `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 whether the call correctly returned the expected magic value
     */
    function checkAndCallSafeTransfer(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) internal returns (bool) {
        if (!_to.isContract()) {
            return true;
        }
        bytes4 retval = ERC721Receiver(_to).onERC721Received(
            _from,
            _tokenId,
            _data
        );
        return (retval == ERC721_RECEIVED);
    }

    /**
     * @dev Gets the token ID at a given index of the tokens list of the requested owner
     * @param _owner address owning the tokens list to be accessed
     * @param _index uint256 representing the index to be accessed of the requested tokens list
     * @return uint256 token ID at the given index of the tokens list owned by the requested address
     */
    function tokenOfOwnerByIndex(address _owner, uint256 _index)
        public
        view
        returns (uint256)
    {
        require(_index < balanceOf(_owner), "Invalid index");
        return ownedTokens[_owner][_index];
    }

    /**
     * @dev Gets the total amount of tokens stored by the contract
     * @return uint256 representing the total amount of tokens
     */
    function totalSupply() public view returns (uint256) {
        return allTokens.length;
    }

    /**
     * @dev Gets the token ID at a given index of all the tokens in this contract
     * Reverts if the index is greater or equal to the total number of tokens
     * @param _index uint256 representing the index to be accessed of the tokens list
     * @return uint256 token ID at the given index of the tokens list
     */
    function tokenByIndex(uint256 _index) public view returns (uint256) {
        require(_index < totalSupply(), "Invalid index");
        return allTokens[_index];
    }

    function supportsInterface(bytes4 _interfaceId)
        external
        view
        returns (bool)
    {
        return supportedInterfaces[_interfaceId];
    }

    function _registerInterface(bytes4 _interfaceId) internal {
        require(_interfaceId != 0xffffffff);
        supportedInterfaces[_interfaceId] = true;
    }
}

// File: ERC721.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

import "./BaseERC721.sol";

contract ERC721 is ERC721BasicToken {
    using Strings for uint256;

    mapping(uint256 => string) internal _tokenUri;
    string public extension;

    event TokenURIUpdated(uint256 tokenId, string _url);
    event BaseTokenURIUpdated(string _baseUrl);

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _baseTokenUri,
        string memory _extension
    ) ERC721BasicToken(_name, _symbol, _baseTokenUri) {
        extension= _extension;
        emit BaseTokenURIUpdated(_baseTokenUri);
    }

    function approve(address _to, uint256 _tokenId) public {
        super._approve(_to, _tokenId);
    }

    function setApprovalForAll(address _to, bool _approved) public {
        super._setApprovalForAll(_to, _approved);
    }

    function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public virtual {
        super._transferFrom(_from, _to, _tokenId);
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public virtual {
        super._safeTransferFrom(_from, _to, _tokenId, "0x");
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) public virtual {
        super._safeTransferFrom(_from, _to, _tokenId, _data);
    }

    function _updateTokenUri(uint256 _tokenId, string memory _url) internal {
        _tokenUri[_tokenId] = _url;
        emit TokenURIUpdated(_tokenId, _url);
    }

    function _updateBaseTokenUri(string memory _baseTokenUri) internal {
        baseTokenURI = _baseTokenUri;
        emit BaseTokenURIUpdated(_baseTokenUri);
    }

    function mint(
        address _to,
        uint256 _tokenId
    ) internal {
        super._mint(_to, _tokenId);
    }

    function burn(uint256 _tokenId) public virtual {
        super._burn(ownerOf(_tokenId), _tokenId);
    }

    function _tokenURI(uint256 _tokenId)
        public
        view
        virtual
        returns (string memory)
    {
        if (bytes(_tokenUri[_tokenId]).length == 0) {
            return string(abi.encodePacked(baseTokenURI, _tokenId.toString(), extension));
        }
        return string(abi.encodePacked(baseTokenURI, _tokenUri[_tokenId], extension));
    }
}

// File: ERC721Receiver.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

/**
 * @title ERC721 token receiver interface
 * @author Prashant Prabhakar Singh [prashantprabhakar123@gmail.com]
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
abstract contract ERC721Receiver {
    /**
     * @dev Magic value to be returned upon successful reception of an NFT
     *  Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`,
     *  which can be also obtained as `ERC721Receiver(0).onERC721Received.selector`
     */
    bytes4 internal constant ERC721_RECEIVED = 0xf0b9e5ba;

    /**
     * @notice Handle the receipt of an NFT
     * @dev The ERC721 smart contract calls this function on the recipient
     * after a `safetransfer`. This function MAY throw to revert and reject the
     * transfer. This function MUST use 50,000 gas or less. Return of other
     * than the magic value MUST result in the transaction being reverted.
     * Note: the contract address is always the message sender.
     * @param _from The sending address
     * @param _tokenId The NFT identifier which is being transfered
     * @param _data Additional data with no specified format
     * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
     */
    function onERC721Received(
        address _from,
        uint256 _tokenId,
        bytes memory _data
    ) public virtual returns (bytes4);
}

// File: IERC721.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

/**
 * @title ERC721 Non-Fungible Token Standard basic interface
 * @author Prashant Prabhakar Singh [prashantprabhakar123@gmail.com]
 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
 */
interface IERC721 {
    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 indexed _tokenId
    );
    event Approval(
        address indexed _owner,
        address indexed _approved,
        uint256 indexed _tokenId
    );
    event ApprovalForAll(
        address indexed _owner,
        address indexed _operator,
        bool _approved
    );

    function balanceOf(address _owner) external view returns (uint256 _balance);

    function ownerOf(uint256 _tokenId) external view returns (address _owner);

    function exists(uint256 _tokenId) external view returns (bool _exists);

    function approve(address _to, uint256 _tokenId) external;

    function getApproved(uint256 _tokenId)
        external
        view
        returns (address _operator);

    function setApprovalForAll(address _operator, bool _approved) external;

    function isApprovedForAll(address _owner, address _operator)
        external
        view
        returns (bool);

    function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) external;

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) external;

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) external;

    function name() external view returns (string memory _name);

    function symbol() external view returns (string memory _symbol);

    function tokenURI(uint256 _tokenId) external view returns (string memory);

    function totalSupply() external view returns (uint256);

    function tokenOfOwnerByIndex(address _owner, uint256 _index)
        external
        view
        returns (uint256 _tokenId);

    function tokenByIndex(uint256 _index) external view returns (uint256);

    function supportsInterface(bytes4 _interfaceId)
        external
        view
        returns (bool);
}

// File: MintPassDummy.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

import "./Ownership.sol";
import "./Addresses.sol";
import "./Strings.sol";
import "./ERC721Receiver.sol";
import "./IERC721.sol";

abstract contract ERC721BasicTokenDummy is IERC721 {
    using Address for address;
    using Strings for uint256;

    bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd;
    bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79;
    bytes4 private constant ERC721_RECEIVED = 0xf0b9e5ba;
    bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63;
    bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f;
    bytes4 private constant InterfaceId_ERC165 = 0x01ffc9a7;

    string internal name_;
    string internal symbol_;
    string public baseTokenURI;

    // Array with all token ids, used for enumeration
    uint256[] internal allTokens;

    // Mapping of interface id to whether or not it's supported
    mapping(bytes4 => bool) internal supportedInterfaces;

    // Mapping from owner to list of owned token IDs
    mapping(address => uint256[]) internal ownedTokens;

    // Mapping from token ID to index of the owner tokens list
    mapping(uint256 => uint256) internal ownedTokensIndex;

    // Mapping from token id to position in the allTokens array
    mapping(uint256 => uint256) internal allTokensIndex;

    // Mapping from token ID to owner
    mapping(uint256 => address) internal tokenOwner;

    // Mapping from token ID to approved address
    mapping(uint256 => address) internal tokenApprovals;

    // Mapping from owner to number of owned token
    mapping(address => uint256) internal ownedTokensCount;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) internal operatorApprovals;

    /**
     * @dev Guarantees msg.sender is owner of the given token
     * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender
     */
    modifier onlyOwnerOf(uint256 _tokenId) {
        require(ownerOf(_tokenId) == msg.sender, "Only asset owner is allowed");
        _;
    }

    /**
     * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator
     * @param _tokenId uint256 ID of the token to validate
     */
    modifier canTransfer(uint256 _tokenId) {
        require(isApprovedOrOwner(msg.sender, _tokenId), "Can not transfer");
        _;
    }

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _baseTokenUri
    ) {
        name_ = _name;
        symbol_ = _symbol;
        baseTokenURI = _baseTokenUri;

        // register the supported interfaces to conform to ERC721 via ERC165
        _registerInterface(InterfaceId_ERC721Enumerable);
        _registerInterface(InterfaceId_ERC721Metadata);
        _registerInterface(InterfaceId_ERC721);
        _registerInterface(InterfaceId_ERC721Exists);
        _registerInterface(InterfaceId_ERC165);
    }

    /**
     * @dev Gets the token name
     * @return string representing the token name
     */
    function name() external view returns (string memory) {
        return name_;
    }

    /**
     * @dev Gets the token symbol
     * @return string representing the token symbol
     */
    function symbol() external view returns (string memory) {
        return symbol_;
    }

    /**
     * @dev Returns an URI for a given token ID
     * Throws if the token ID does not exist. May return an empty string.
     * @param _tokenId uint256 ID of the token to query
     */
    function tokenURI(uint256 _tokenId)
        public
        view
        virtual
        returns (string memory)
    {
        require(exists(_tokenId), "Asset does not exist");
        return string(abi.encodePacked(baseTokenURI, _tokenId.toString()));
    }

    /**
     * @dev Gets the balance of the specified address
     * @param _owner address to query the balance of
     * @return uint256 representing the amount owned by the passed address
     */
    function balanceOf(address _owner) public view returns (uint256) {
        require(_owner != address(0), "Zero address not allowed");
        return ownedTokensCount[_owner];
    }

    /**
     * @dev Gets the owner of the specified token ID
     * @param _tokenId uint256 ID of the token to query the owner of
     * @return owner address currently marked as the owner of the given token ID
     */
    function ownerOf(uint256 _tokenId) public view returns (address) {
        address owner = tokenOwner[_tokenId];
        require(owner != address(0), "Zero address not allowed");
        return owner;
    }

    /**
     * @dev Returns whether the specified token exists
     * @param _tokenId uint256 ID of the token to query the existence of
     * @return whether the token exists
     */
    function exists(uint256 _tokenId) public view returns (bool) {
        address owner = tokenOwner[_tokenId];
        return owner != address(0);
    }

    /**
     * @dev Approves another address to transfer the given token ID
     * The zero address indicates there is no approved address.
     * There can only be one approved address per token at a given time.
     * Can only be called by the token owner or an approved operator.
     * @param _to address to be approved for the given token ID
     * @param _tokenId uint256 ID of the token to be approved
     */
    function _approve(address _to, uint256 _tokenId) internal {
        address owner = ownerOf(_tokenId);
        require(_to != owner, "Can not approve to self");
        require(
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "Not allowed to update approvals"
        );

        tokenApprovals[_tokenId] = _to;
        emit Approval(owner, _to, _tokenId);
    }

    /**
     * @dev Gets the approved address for a token ID, or zero if no address set
     * @param _tokenId uint256 ID of the token to query the approval of
     * @return address currently approved for the given token ID
     */
    function getApproved(uint256 _tokenId) public view returns (address) {
        return tokenApprovals[_tokenId];
    }

    /**
     * @dev Sets or unsets the approval of a given operator
     * An operator is allowed to transfer all tokens of the sender on their behalf
     * @param _to operator address to set the approval
     * @param _approved representing the status of the approval to be set
     */
    function _setApprovalForAll(address _to, bool _approved) internal {
        require(_to != msg.sender, "Can not approve to self");
        operatorApprovals[msg.sender][_to] = _approved;
        emit ApprovalForAll(msg.sender, _to, _approved);
    }

    /**
     * @dev Tells whether an operator is approved by a given owner
     * @param _owner owner address which you want to query the approval of
     * @param _operator operator address which you want to query the approval of
     * @return bool whether the given operator is approved by the given owner
     */
    function isApprovedForAll(address _owner, address _operator)
        public
        view
        returns (bool)
    {
        return operatorApprovals[_owner][_operator];
    }

    /**
     * @dev Transfers the ownership of a given token ID to another address
     * Usage of this method is discouraged, use `safeTransferFrom` whenever possible
     * Requires the msg sender to be the owner, approved, or operator
     * @param _from current owner of the token
     * @param _to address to receive the ownership of the given token ID
     * @param _tokenId uint256 ID of the token to be transferred
     */
    function _transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) internal canTransfer(_tokenId) {
        require(_from != address(0), "Zero address not allowed");
        require(_to != address(0), "Zero address not allowed");

        clearApproval(_from, _tokenId);
        removeTokenFrom(_from, _tokenId);
        addTokenTo(_to, _tokenId);

        emit Transfer(_from, _to, _tokenId);
    }

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     *
     * Requires the msg sender to be the owner, approved, or operator
     * @param _from current owner of the token
     * @param _to address to receive the ownership of the given token ID
     * @param _tokenId uint256 ID of the token to be transferred
     */
    function _safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) internal canTransfer(_tokenId) {
        // solium-disable-next-line arg-overflow
        _safeTransferFrom(_from, _to, _tokenId, "");
    }

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     * Requires the msg sender to be the owner, approved, or operator
     * @param _from current owner of the token
     * @param _to address to receive the ownership of the given token ID
     * @param _tokenId uint256 ID of the token to be transferred
     * @param _data bytes data to send along with a safe transfer check
     */
    function _safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) internal canTransfer(_tokenId) {
        _transferFrom(_from, _to, _tokenId);
        // solium-disable-next-line arg-overflow
        require(
            checkAndCallSafeTransfer(_from, _to, _tokenId, _data),
            "Safe Transfer failed"
        );
    }

    /**
     * @dev Returns whether the given spender can transfer a given token ID
     * @param _spender address of the spender to query
     * @param _tokenId uint256 ID of the token to be transferred
     * @return bool whether the msg.sender is approved for the given token ID,
     *  is an operator of the owner, or is the owner of the token
     */
    function isApprovedOrOwner(address _spender, uint256 _tokenId)
        internal
        view
        returns (bool)
    {
        address owner = ownerOf(_tokenId);
        // Disable solium check because of
        // https://github.com/duaraghav8/Solium/issues/175
        // solium-disable-next-line operator-whitespace
        return (_spender == owner ||
            getApproved(_tokenId) == _spender ||
            isApprovedForAll(owner, _spender));
    }

    /**
     * @dev Internal function to mint a new token
     * Reverts if the given token ID already exists
     * @param _to The address that will own the minted token
     * @param _tokenId uint256 ID of the token to be minted by the msg.sender
     */
    function _mint(address _to, uint256 _tokenId) internal {
        require(_to != address(0), "Zero address not allowed");
        addTokenTo(_to, _tokenId);
        allTokensIndex[_tokenId] = allTokens.length;
        allTokens.push(_tokenId);

        emit Transfer(address(0), _to, _tokenId);
    }

    /**
     * @dev Internal function to burn a specific token
     * Reverts if the token does not exist
     * @param _tokenId uint256 ID of the token being burned by the msg.sender
     */
    function _burn(address _owner, uint256 _tokenId)
        internal
        canTransfer(_tokenId)
    {
        clearApproval(_owner, _tokenId);
        removeTokenFrom(_owner, _tokenId);

        // Reorg all tokens array
        uint256 tokenIndex = allTokensIndex[_tokenId];
        uint256 lastTokenIndex = allTokens.length - 1;
        uint256 lastToken = allTokens[lastTokenIndex];

        allTokens[tokenIndex] = lastToken;
        delete allTokens[lastTokenIndex];

        //allTokens[lastTokenIndex] = 0;
        // allTokens.length--; // @TODO

        allTokensIndex[_tokenId] = 0;
        allTokensIndex[lastToken] = tokenIndex;
        emit Transfer(_owner, address(0), _tokenId);
    }

    /**
     * @dev Internal function to clear current approval of a given token ID
     * Reverts if the given address is not indeed the owner of the token
     * @param _owner owner of the token
     * @param _tokenId uint256 ID of the token to be transferred
     */
    function clearApproval(address _owner, uint256 _tokenId) internal {
        require(
            ownerOf(_tokenId) == _owner,
            "Asset does not belong to given owmer"
        );
        if (tokenApprovals[_tokenId] != address(0)) {
            tokenApprovals[_tokenId] = address(0);
            emit Approval(_owner, address(0), _tokenId);
        }
    }

    /**
     * @dev Internal function to add a token ID to the list of a given address
     * @param _to address representing the new owner of the given token ID
     * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function addTokenTo(address _to, uint256 _tokenId) internal {
        require(tokenOwner[_tokenId] == address(0), "Asset already exists");
        tokenOwner[_tokenId] = _to;
        ownedTokensCount[_to] = ownedTokensCount[_to] + 1;
        uint256 length = ownedTokens[_to].length;
        ownedTokens[_to].push(_tokenId);
        ownedTokensIndex[_tokenId] = length;
    }

    /**
     * @dev Internal function to remove a token ID from the list of a given address
     * @param _from address representing the previous owner of the given token ID
     * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function removeTokenFrom(address _from, uint256 _tokenId) internal {
        require(
            ownerOf(_tokenId) == _from,
            "Asset does not belong to given owmer"
        );
        ownedTokensCount[_from] = ownedTokensCount[_from] - 1;
        tokenOwner[_tokenId] = address(0);
        uint256 tokenIndex = ownedTokensIndex[_tokenId];
        uint256 lastTokenIndex = ownedTokens[_from].length - 1;
        uint256 lastToken = ownedTokens[_from][lastTokenIndex];

        ownedTokens[_from][tokenIndex] = lastToken;
        ownedTokens[_from][lastTokenIndex] = 0;
        // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to
        // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping
        // the lastToken to the first position, and then dropping the element placed in the last position of the list

        //ownedTokens[_from].length--; @TODO
        ownedTokensIndex[_tokenId] = 0;
        ownedTokensIndex[lastToken] = tokenIndex;
    }

    /**
     * @dev Internal function to invoke `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 whether the call correctly returned the expected magic value
     */
    function checkAndCallSafeTransfer(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) internal returns (bool) {
        if (!_to.isContract()) {
            return true;
        }
        bytes4 retval = ERC721Receiver(_to).onERC721Received(
            _from,
            _tokenId,
            _data
        );
        return (retval == ERC721_RECEIVED);
    }

    /**
     * @dev Gets the token ID at a given index of the tokens list of the requested owner
     * @param _owner address owning the tokens list to be accessed
     * @param _index uint256 representing the index to be accessed of the requested tokens list
     * @return uint256 token ID at the given index of the tokens list owned by the requested address
     */
    function tokenOfOwnerByIndex(address _owner, uint256 _index)
        public
        view
        returns (uint256)
    {
        require(_index < balanceOf(_owner), "Invalid index");
        return ownedTokens[_owner][_index];
    }

    /**
     * @dev Gets the total amount of tokens stored by the contract
     * @return uint256 representing the total amount of tokens
     */
    function totalSupply() public view returns (uint256) {
        return allTokens.length;
    }

    /**
     * @dev Gets the token ID at a given index of all the tokens in this contract
     * Reverts if the index is greater or equal to the total number of tokens
     * @param _index uint256 representing the index to be accessed of the tokens list
     * @return uint256 token ID at the given index of the tokens list
     */
    function tokenByIndex(uint256 _index) public view returns (uint256) {
        require(_index < totalSupply(), "Invalid index");
        return allTokens[_index];
    }

    function supportsInterface(bytes4 _interfaceId)
        external
        view
        returns (bool)
    {
        return supportedInterfaces[_interfaceId];
    }

    function _registerInterface(bytes4 _interfaceId) internal {
        require(_interfaceId != 0xffffffff);
        supportedInterfaces[_interfaceId] = true;
    }
}

contract ERC721Dummy is ERC721BasicTokenDummy {
    mapping(uint256 => string) internal _tokenUri;

    event TokenURIUpdated(uint256 tokenId, string _url);
    event BaseTokenURIUpdated(string _baseUrl);

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _baseTokenUri
    ) ERC721BasicTokenDummy(_name, _symbol, _baseTokenUri) {
        emit BaseTokenURIUpdated(_baseTokenUri);
    }

    function approve(address _to, uint256 _tokenId) public {
        super._approve(_to, _tokenId);
    }

    function setApprovalForAll(address _to, bool _approved) public {
        super._setApprovalForAll(_to, _approved);
    }

    function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public virtual {
        super._transferFrom(_from, _to, _tokenId);
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public virtual {
        super._safeTransferFrom(_from, _to, _tokenId, "0x");
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) public virtual {
        super._safeTransferFrom(_from, _to, _tokenId, _data);
    }

    function _updateTokenUri(uint256 _tokenId, string memory _url) internal {
        _tokenUri[_tokenId] = _url;
        emit TokenURIUpdated(_tokenId, _url);
    }

    function _updateBaseTokenUri(string memory _baseTokenUri) internal {
        baseTokenURI = _baseTokenUri;
        emit BaseTokenURIUpdated(_baseTokenUri);
    }

    function mint(
        address _to,
        uint256 _tokenId,
        string memory _url
    ) internal {
        _updateTokenUri(_tokenId, _url);
        super._mint(_to, _tokenId);
    }

    function burn(uint256 _tokenId) public virtual {
        super._burn(ownerOf(_tokenId), _tokenId);
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(exists(_tokenId), "Asset does not exist");
        return string(abi.encodePacked(baseTokenURI, _tokenUri[_tokenId]));
    }
}

contract MintPass is ERC721Dummy, Ownership {
    // represents how many times a mint pass is redeemed.
    mapping(uint256 => uint8) private redeemedTimes;
    // contracts that can update mint pass uses, like when minting art, art nft will updated `redeemedTimes`
    mapping(address => bool) public isWhitelisted;
    // nonce to prevent replay attack on admin signature
    mapping(uint256 => bool) public isSignerNonceUsed;

    bool public mintStoppted; // when enabled no futher mint pass can be minted.
    uint256 public maxSupply; // max supply of mint pass
    bool public isPaused = false; // pause the contractn when something goes "really" wrong

    uint8 constant UINT8_MAX = 255;

    struct Signature {
        bytes32 r;
        bytes32 s;
        uint8 v;
    }

    event MintPassUpdated(
        uint256 tokenId,
        uint8 redeemedTimes,
        address updatedBy
    );

    event Paused(bool _isPaused);

    modifier canAcceptMintPass(address user) {
        require(balanceOf(user) == 0, "Only 1 mint pass allowed per user");
        _;
    }

    modifier mintAllowed() {
        require(!mintStoppted, "Minting stopped");
        _;
    }

    modifier notPaused() {
        require(!isPaused, "Contract paused");
        _;
    }

    constructor(string memory _baseTokenUri)
        ERC721Dummy("Thunderbirds: IRC Mint Pass", "TBMP", _baseTokenUri)
    {
        maxSupply = 1000;
    }

    function mint(
        address user,
        Signature memory adminSignature,
        uint256 signerNonce
    ) public canAcceptMintPass(user) mintAllowed notPaused {
        require(totalSupply() < maxSupply, "Max token minted");
        require(
            !isSignerNonceUsed[signerNonce],
            "Duplicate nonce in signature"
        );
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes4(keccak256("mint")),
                address(this),
                signerNonce,
                getChainID(),
                user
            )
        );
        address signer = getSigner(hash, adminSignature);
        require(isDeputyOwner[signer], "Invalid signature/message");
        isSignerNonceUsed[signerNonce] = true;
        string memory url = "QmXyV2HUP7hv8Xjx3X6ZLUUMHkxsdRuQhZ9DwpzZqH16jD";
        super.mint(user, totalSupply() + 1, url);
    }

    function batchMintByAdmin(address[] memory users) public mintAllowed onlyDeputyOrOwner {
        require(totalSupply() + users.length < maxSupply, "Max token minted");
        string memory url = "QmXyV2HUP7hv8Xjx3X6ZLUUMHkxsdRuQhZ9DwpzZqH16jD";
        for(uint8 i=0; i<users.length; i++) {
            require(balanceOf(users[i]) == 0, "Only 1 mint pass allowed per user");
            super.mint(users[i], totalSupply() + 1, url);
        }
    }

    function updateRedeemedTimes(uint256 tokenId, uint8 _redeemedTimes)
        public
        notPaused
    {
        require(isWhitelisted[msg.sender], "Caller not whitelisted");
        redeemedTimes[tokenId] = _redeemedTimes;
        emit MintPassUpdated(tokenId, _redeemedTimes, msg.sender);
    }

    function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public override canAcceptMintPass(_to) notPaused {
        super._transferFrom(_from, _to, _tokenId);
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public override canAcceptMintPass(_to) notPaused {
        super._safeTransferFrom(_from, _to, _tokenId, "0x");
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) public override canAcceptMintPass(_to) notPaused {
        super._safeTransferFrom(_from, _to, _tokenId, _data);
    }

    function burn(uint256 _tokenId) public override notPaused {
        super.burn(_tokenId);
    }
    

    function updateTokenUri(uint256 _tokenId, string memory _url)
        public
        onlyDeputyOrOwner
    {
        super._updateTokenUri(_tokenId, _url);
    }

    function updateBaseTokenUri(string memory _baseTokenUri) public onlyOwner {
        super._updateBaseTokenUri(_baseTokenUri);
    }

    function whitelistContract(address contractAddress) public onlyOwner {
        isWhitelisted[contractAddress] = true;
    }

    function removeFromWhitelist(address contractAddress) public onlyOwner {
        isWhitelisted[contractAddress] = false;
    }

    function disableMinting(bool shoudlStop) public onlyOwner {
        mintStoppted = shoudlStop;
    }

    function pauseContract(bool _isPaused) public onlyOwner returns (bool) {
        isPaused = _isPaused;
        emit Paused(_isPaused);
        return true;
    }

    function getSigner(bytes32 message, Signature memory sig)
        public
        pure
        returns (address)
    {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, message));
        address signer = ecrecover(prefixedHash, sig.v, sig.r, sig.s);
        return signer;
    }

    function getRedeemedTimes(uint256 tokenId) public view returns(uint8) {
        if(!exists(tokenId)) return UINT8_MAX;
        return redeemedTimes[tokenId];
    }

    function getChainID() internal view returns (uint256) {
        uint256 id;
        assembly {
            id := chainid()
        }
        return id;
    }
}

// File: Ownership.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

contract Ownership {
    address public owner;
    address[] public deputyOwners;

    mapping(address => bool) public isDeputyOwner;

    event OwnershipUpdated(address oldOwner, address newOwner);
    event DeputyOwnerUpdated(address _do, bool _isAdded);

    constructor() {
        owner = msg.sender;
        addDeputyOwner(msg.sender);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Caller is not owner");
        _;
    }

    modifier onlyDeputyOrOwner() {
        require(
            msg.sender == owner || isDeputyOwner[msg.sender],
            "Caller is not owner or deputy"
        );
        _;
    }

    /**
     * @dev Transfer the ownership to some other address.
     * new owner can not be a zero address.
     * Only owner can call this function
     * @param _newOwner Address to which ownership is being transferred
     */
    function updateOwner(address _newOwner) public onlyOwner {
        require(_newOwner != address(0x0), "Invalid address");
        owner = _newOwner;
        emit OwnershipUpdated(msg.sender, owner);
    }

    /**
     * @dev Add new deputy owner.
     * Only Owner can call this function
     * New Deputy should not be zero address
     * New Deputy should not be be already exisitng
     * emit DeputyOwnerUdpatd event
     * @param _newDO Address of new deputy owner
     */
    function addDeputyOwner(address _newDO) public onlyOwner {
        require(!isDeputyOwner[_newDO], "Deputy Owner already exists");
        deputyOwners.push(_newDO);
        isDeputyOwner[_newDO] = true;
        emit DeputyOwnerUpdated(_newDO, true);
    }

    /**
     * @dev Remove an existing deputy owner.
     * Only Owner can call this function
     * Given address should be a deputy owner
     * emit DeputyOwnerUdpatd event
     * @param _existingDO Address of existing deputy owner
     */
    function removeDeputyOwner(address _existingDO) public onlyOwner {
        require(isDeputyOwner[_existingDO], "Deputy Owner does not exits");
        uint256 existingId;
        for (uint256 i = 0; i < deputyOwners.length; i++) {
            if (deputyOwners[i] == _existingDO) existingId = i;
        }

        // swap this with last element
        deputyOwners[existingId] = deputyOwners[deputyOwners.length - 1];
        delete deputyOwners[deputyOwners.length - 1];
        isDeputyOwner[_existingDO] = false;
        emit DeputyOwnerUpdated(_existingDO, false);
    }

    /**
     * @dev Renounce the ownership.
     * This will leave the contract without any owner.
     * Only owner can call this function
     * @param _validationCode A code to prevent accidental calling of this function
     */
    function renounceOwnership(uint256 _validationCode) public onlyOwner {
        require(_validationCode == 123456789, "Invalid code");
        owner = address(0);
        emit OwnershipUpdated(msg.sender, owner);
    }
}

// File: Strings.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

/**
 * @dev String operations.
 */
library Strings {
    /**
     * @dev Converts a `uint256` to its ASCII `string` 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);
    }
}

// File: Thunderbirds.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

import "./ERC721.sol";
import "./Ownership.sol";

interface IMintPass {
    function getRedeemedTimes(uint256 tokenId) external view returns(uint8);
    function ownerOf(uint256 _tokenId) external view returns (address);
    function balanceOf(address _owner) external view returns (uint256);
    function exists(uint256 _tokenId) external view returns (bool);
    function tokenOfOwnerByIndex(address _owner, uint256 _index)
        external
        view
        returns (uint256 _tokenId);
    function updateRedeemedTimes(uint256 tokenId, uint8 _redeemedTimes) external;
}

contract Thunderbirds is ERC721, Ownership {

    uint256 public maxSupply; // max supply of nft
    uint256 public preSaleStarts;
    uint256 public preSaleEnds;
    uint256 public saleEnds;
    IMintPass private mintPass;
    bool public isPaused = false; // pause the contractn when something goes "really" wrong
    uint256 public price;
    address payable public coldWallet;
    uint256 public mintedForTeam;
    bool public isRevealed;

    uint256 private constant reservedForTeam = 30;
    uint8 private constant MAX_REDEEME_COUNT = 3;

    // nonce to prevent replay attack on admin signature
    mapping(address => mapping(uint => bool)) public isSignerNonceUsed;

    struct Signature {
        bytes32 r;
        bytes32 s;
        uint8 v;
    }


    event Paused(bool _isPaused);
    event PriceUpdate(uint256 oldPrice, uint256 newPrice);


    modifier notPaused() {
        require(!isPaused, "Contract paused");
        _;
    }
    

    modifier canMint(uint8 quantity) {
        require(block.timestamp >= preSaleStarts , "Sale not live yet");
        require(block.timestamp < saleEnds, "Sale closed");
        require(totalSupply() + quantity <= maxSupply - reservedForTeam, "Sold out");
        require(msg.value == price*quantity, "Incorrect fee");
        _;
    }


    constructor(
        address _mintPassContract,
        uint256 _preSaleStarts,
        uint256 _preSaleEnds,
        address payable _coldWallet,
        uint256 _price,
        string memory _baseurl
    )
        ERC721("Thunderbirds International Rescue Club", "FAB", _baseurl, ".json")
    {
        mintPass = IMintPass(_mintPassContract);
        maxSupply = 5432;
        price = _price;
        coldWallet = _coldWallet;
        preSaleStarts = _preSaleStarts;
        preSaleEnds = _preSaleEnds;
        saleEnds = preSaleEnds + 1 weeks;
        isRevealed = false;
    }

    function mint(address user, uint8 quantity) public payable canMint(quantity) notPaused {
        require(block.timestamp >= preSaleEnds, "Only mintpass holders allowed in presale");
        coldWallet.transfer(msg.value);
        _batchMint(user, quantity);
    }

    function mintWithPass(address user, uint8 quantity, uint256 mintPassId) public payable canMint(quantity) {
        require(block.timestamp < preSaleEnds, "Pre-sale ended");
        require(mintPass.exists(mintPassId), "Invalid mint pass");
        require(mintPass.ownerOf(mintPassId) == msg.sender, "Sender does not own given mint pass");
        coldWallet.transfer(msg.value);
        uint8 redeemedTimes = mintPass.getRedeemedTimes(mintPassId);
        require(redeemedTimes+quantity <= MAX_REDEEME_COUNT, "Mint pass redeemed");
        mintPass.updateRedeemedTimes(mintPassId, redeemedTimes+quantity);
        _batchMint(user, quantity);
    }

    function mintReservedTokens(address[] memory users) public onlyDeputyOrOwner {
        require(mintedForTeam + users.length <= reservedForTeam, "Max reserved tokens minted");
        unchecked {
            mintedForTeam += users.length;
        }
        for(uint8 i=0; i<users.length; i++) {
            super.mint(users[i], totalSupply()+1);
        }
    }


    function updatePrice(uint256 _price) public onlyOwner {
        emit PriceUpdate(price, _price);
        price = _price;
    }


    function _batchMint(address user, uint8 quantity) internal  {
        for(uint8 i=0; i<quantity; i++) {
            super.mint(user, totalSupply() + 1);
        }
    }

    
    function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public override notPaused {
        super._transferFrom(_from, _to, _tokenId);
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    ) public override notPaused {
        super._safeTransferFrom(_from, _to, _tokenId, "0x");
    }

    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes memory _data
    ) public override notPaused {
        super._safeTransferFrom(_from, _to, _tokenId, _data);
    }

    function burn(uint256 _tokenId) public override notPaused {
        super.burn(_tokenId);
    }
    
    function preAuthTransfer(
        address _from, address _to, uint256 _tokenId, uint256 signerNonce, Signature memory signature
    ) public notPaused {
        require(
            !isSignerNonceUsed[_from][signerNonce],
            "Duplicate nonce in signature"
        );

        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes4(keccak256("transfer")),
                address(this),
                signerNonce,
                getChainID(),
                _from,
                _to,
                _tokenId
            )
        );
        address signer = getSigner(hash, signature);
        require(signer == _from, "Owner and signer don't match");
        isSignerNonceUsed[signer][signerNonce] = true;
        super._transferOnBehalf(signer, _to, _tokenId);
    }

    function updateBaseTokenUri(string memory _baseTokenUri) public onlyOwner {
        super._updateBaseTokenUri(_baseTokenUri);
    }

    function pauseContract(bool _isPaused) public onlyOwner{
        isPaused = _isPaused;
        emit Paused(_isPaused);
    }

    function reveal() public onlyOwner {
        isRevealed = true;
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(exists(_tokenId), "Asset does not exist");
        if(!isRevealed) return baseTokenURI;
        return super._tokenURI(_tokenId);
    }

    function getSigner(bytes32 message, Signature memory sig)
        public
        pure
        returns (address)
    {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, message));
        address signer = ecrecover(prefixedHash, sig.v, sig.r, sig.s);
        return signer;
    }

    function getChainID() internal view returns (uint256) {
        uint256 id;
        assembly {
            id := chainid()
        }
        return id;
    }

}