// File: contracts/ERC721Airdrop.sol




// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "./ERC721Enumerable.sol";
import "./ERC721Metadata.sol";
import "./ERC721Burnable.sol";
import "./Ownable.sol";

contract ERC721Airdrop is
    ERC721Enumerable,
    ERC721Burnable,
    ERC721Metadata,
    Ownable
{
  function airdrop(address[] memory receivers, bytes32[] memory tokenURIs, bytes calldata _data) public onlyOwner{
    uint256 l = receivers.length;
    require(l == tokenURIs.length, "ERC721Airdrop: receivers and tokenURIs length mismatch");
    // we get the last token id and start from there
    uint256 tokenId = _owners.length;
    for(uint256 i = 0; i < l; i++){
      // we add the index to the tokenId so that we don't need to call length each loop
      _mintAndTransfer(msg.sender, receivers[i], tokenId + i, _data);
      _setTokenURI(tokenId + i, tokenURIs[i]);
    }
  }

  function mint(bytes32 _tokenURI, address _to) external onlyOwner {
    uint256 tokenId = _owners.length;
    _mint(_to, tokenId);
    _setTokenURI(tokenId, _tokenURI);
  }

  function mintAndTransfer(address _to, bytes32 _tokenURI, bytes calldata _data) external onlyOwner {
    uint256 tokenId = _owners.length;
    _mintAndTransfer(msg.sender, _to, tokenId, _data);
    _setTokenURI(tokenId, _tokenURI);
  }

  function _burn(uint256 tokenId) internal override(ERC721, ERC721Metadata) {
    super._burn(tokenId);
  }

  function exists(uint256 _id) external view returns (bool) {
    return _owners[_id] != ZERO_ADDRESS;
  }

  function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
    return interfaceId == 0x80ac58cd // type(IERC721).interfaceId
        || interfaceId == 0x780e9d63 // type(IERC721Enumerable).interfaceId
        || interfaceId == 0x5b5e139f; // type(IERC721Metadata).interfaceId
  }

  constructor(string memory _name, string memory _symbol) {
    name = _name;
    symbol = _symbol;
  }

}
// File: contracts/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 * @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 Ownable {
    address private _owner;

    event OwnershipTransferred(address previousOwner, address newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(msg.sender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == msg.sender);
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() external onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0));
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: contracts/ERC721Burnable.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import "./ERC721.sol";

/**
 * @title ERC721 Burnable Token
 * @dev ERC721 Token that can be burned (destroyed).
 */
abstract contract ERC721Burnable is ERC721 {
    /**
     * @dev Burns `tokenId`. See {ERC721-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 _tokenId) external {
        require(_isApprovedOrOwner(msg.sender, _tokenId));
        _burn(_tokenId);
    }
}

// File: contracts/ERC721Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0)
// (token/ERC721/extensions/ERC721Metadata.sol)

pragma solidity 0.8.19;

import "./ERC721.sol";
import "./DecodeTokenURI.sol";

/**
 * @dev ERC721 token with storage based token URI management.
 */
abstract contract ERC721Metadata is ERC721 {
    using DecodeTokenURI for bytes;

    // Token name
    string public name;

    // Token symbol
    string public symbol;

    /**
     * @dev Hardcoded base URI in order to remove the need for a constructor, it
     * can be set anytime by an admin
     * (multisig).
     */
    string private _baseTokenURI = "ipfs://";

    /**
     * @dev Optional mapping for token URIs
     */
    mapping(uint256 => bytes32) private _tokenURIs;

    /**
     * @dev See {IERC721Metadata-tokenURI}. It needs to be overridden because
     * the new OZ contracts concatenate _baseURI
     * + tokenId instead of _baseURI + _tokenURI
     */
    function tokenURI(uint256 tokenId) external view returns (string memory) {
        require(_exists(tokenId), "ERC721: nonexistent token");

        return string( // once hex encoded base58 is converted to string, we get
            // the initial IPFS hash
            abi.encodePacked(
                _baseTokenURI,
                abi.encodePacked(bytes2(0x1220), _tokenURIs[tokenId]) // full
                    // bytes of base58 + hex encoded IPFS hash
                    // example.
                    // prepending 2 bytes IPFS hash identifier that was removed
                    // before storing the hash in order to
                    // fit in bytes32. 0x1220 is "Qm" base58 and hex encoded
                    // tokenURI (IPFS hash) with its first 2 bytes truncated,
                    // base58 and hex encoded returned as
                    // bytes32
                    .toBase58()
            )
        );
    }

    /**
     * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
     * @dev only called when a new token is minted, therefore
     * `require(_exists(tokenId))` check was removed
     * Since Openzeppelin contracts v4.0 the _setTokenURI() function was
     * removed, instead we must append the tokenID
     * directly to this variable returned by _baseURI() internal function.
     * This contract implements all the required functionality from
     * ERC721Metadata, which is the OpenZeppelin
     * extension for supporting _setTokenURI.
     * See
     * https://forum.openzeppelin.com/t/why-doesnt-openzeppelin-erc721-contain-settokenuri/6373
     * and
     * https://forum.openzeppelin.com/t/function-settokenuri-in-erc721-is-gone-with-pragma-0-8-0/5978/2
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _setTokenURI(uint256 tokenId, bytes32 _tokenURI) internal {
        _tokenURIs[tokenId] = _tokenURI;
    }

    /**
     * @notice Optional function to set the base URI
     * @dev child contract MAY require access control to the external function
     * implementation
     */
    function _setBaseURI(string calldata baseURI_) internal {
        // require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender));
        _baseTokenURI = baseURI_;
    }

    /**
     * @dev See {ERC721-_burn}.
     */
    function _burn(uint256 _tokenId) internal virtual override {
        super._burn(_tokenId);

        delete _tokenURIs[_tokenId];
    }
}

// File: contracts/ERC721Enumerable.sol



/*----------------------------------------------------------*|
|*          ███    ██ ██ ███    ██ ███████  █████           *|
|*          ████   ██ ██ ████   ██ ██      ██   ██          *|
|*          ██ ██  ██ ██ ██ ██  ██ █████   ███████          *|
|*          ██  ██ ██ ██ ██  ██ ██ ██      ██   ██          *|
|*          ██   ████ ██ ██   ████ ██      ██   ██          *|
|*----------------------------------------------------------*/

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "./ERC721.sol";

/**
 *
 * @title ERC721Enumerable                                   *
 *                                                           *
 * @dev This implements an optional extension of {ERC721}    *
 *      defined in the EIP that adds enumerability of all    *
 *      the token ids in the contract as well as all token   *
 *      ids owned by each account.                           *
 *                                                           *
 * @custom:security-contact tech@ninfa.io                    *
 *
 */
abstract contract ERC721Enumerable is ERC721 {
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256) {
        require(_owner == ownerOf(_index));
        return _index;
    }

    /// @notice Count NFTs tracked by this contract
    /// @return A count of valid NFTs tracked by this contract, where each one
    /// of
    ///  them has an assigned and queryable owner not equal to the zero address
    function totalSupply() external view returns (uint256) {
        return _owners.length;
    }

    /// @notice Enumerate valid NFTs
    /// @dev Throws if `_index` >= `totalSupply()`.
    /// @param _index A counter less than `totalSupply()`
    /// @return The token identifier for the `_index`th NFT,
    ///  (sort order not specified)
    function tokenByIndex(uint256 _index) external view returns (uint256) {
        require(_exists(_index));
        return _index;
    }
}

// File: contracts/DecodeTokenURI.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/// @dev stripped down version of https://github.com/MrChico/verifyIPFS/
library DecodeTokenURI {
    bytes constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

    /**
     * @dev Converts hex string to base 58
     */
    function toBase58(bytes memory source) internal pure returns (bytes memory) {
        if (source.length == 0) return new bytes(0);
        uint8[] memory digits = new uint8[](64);
        digits[0] = 0;
        uint8 digitlength = 1;
        for (uint256 i = 0; i < source.length; ++i) {
            uint256 carry = uint8(source[i]);
            for (uint256 j = 0; j < digitlength; ++j) {
                carry += uint256(digits[j]) * 256;
                digits[j] = uint8(carry % 58);
                carry = carry / 58;
            }

            while (carry > 0) {
                digits[digitlength] = uint8(carry % 58);
                digitlength++;
                carry = carry / 58;
            }
        }
        return toAlphabet(reverse(truncate(digits, digitlength)));
    }

    function toAlphabet(uint8[] memory indices) private pure returns (bytes memory) {
        bytes memory output = new bytes(indices.length);
        for (uint256 i = 0; i < indices.length; i++) {
            output[i] = ALPHABET[indices[i]];
        }
        return output;
    }

    function truncate(uint8[] memory array, uint8 length) private pure returns (uint8[] memory) {
        uint8[] memory output = new uint8[](length);
        for (uint256 i = 0; i < length; i++) {
            output[i] = array[i];
        }
        return output;
    }

    function reverse(uint8[] memory input) private pure returns (uint8[] memory) {
        uint8[] memory output = new uint8[](input.length);
        for (uint256 i = 0; i < input.length; i++) {
            output[i] = input[input.length - 1 - i];
        }
        return output;
    }
}

// File: contracts/ERC721.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import "./IERC721Receiver.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721]
 * Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is
 * available separately as
 * {ERC721Enumerable}.
 * @dev removed constructor in order to allow name and symbol to be set by
 * facory clones contracts via the `initialize`
 * function instead.
 *      name and symbol should be set in most derived contract's constructor
 * instead
 */
contract ERC721 {
    // array of token owners, accessed in {NinfaERC721-totalSupply}
    address[] internal _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;

    // util to save on gas in various functions
    address internal constant ZERO_ADDRESS = address(0);

    /**
     * @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 See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view returns (address _owner) {
        _owner = _owners[tokenId];
        if (_owner == ZERO_ADDRESS) revert();
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) external {
        address owner = ownerOf(tokenId);

        require(msg.sender == owner || _operatorApprovals[owner][msg.sender]);

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view returns (address) {
        require(_exists(tokenId));

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) external {
        _setApprovalForAll(msg.sender, operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(msg.sender, tokenId));

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public {
        require(_isApprovedOrOwner(msg.sender, _tokenId));
        _safeTransfer(_from, _to, _tokenId, _data);
    }

    /**
     * @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.
     *
     * `_data` is additional data, it has no specified format and it is sent in
     * call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be
     * used to e.g.
     * implement alternative mechanisms to perform token transfer, such as
     * signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement
     * {IERC721Receiver-onERC721Received}, which is called upon
     * a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) private {
        _transfer(_from, _to, _tokenId);
        require(_checkOnERC721Received(_from, _to, _tokenId, _data));
    }

    /**
     * @dev Destroys `tokenId`.
     *      The approval is cleared when the token is burned.
     *      This is an internal function that does not check if the sender is
     * authorized to operate on the token.
     *      Emits a {Transfer} event.
     * @param _tokenId MUST exist.
     */
    function _burn(uint256 _tokenId) internal virtual {
        // Clear approvals
        delete _tokenApprovals[_tokenId];

        delete _owners[_tokenId]; // equivalent to Openzeppelin's
            // `_balances[owner] -= 1`

        emit Transfer(msg.sender, ZERO_ADDRESS, _tokenId);
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve}
     * or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view returns (bool) {
        return _owners[tokenId] != ZERO_ADDRESS;
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        require(_exists(tokenId));
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || _operatorApprovals[owner][spender]);
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`. Doesn't support safe
     * transfers while minting, i.e. doesn't call
     * onErc721Received function because when minting the receiver is
     * msg.sender.
     * We don’t need to zero address check because msg.sender is never the
     * zero address.
     * Because the tokenId is always incremented, we don’t need to check if
     * the token exists already.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address _to, uint256 _tokenId) internal {
        _owners.push(_to);

        emit Transfer(ZERO_ADDRESS, _to, _tokenId);
    }

    /**
     * @dev rather than calling the internal `_mint` function which would create
     * a new owner like so
     * `_owners.push(_to)`,
     *      however ownership will be reassigned/overridden with the buyer's
     * address by the time the `lazyMint` function
     * has finished executing
     *      therefore the `transfer` event is emitted in order to signal to
     * DApps that a mint has occurred
     */
    function _mintAndTransfer(address _minter, address _to, uint256 _tokenId, bytes calldata _data) internal {
        if (_to == ZERO_ADDRESS) revert();
        /*----------------------------------------------------------*|
        |*  # MINT                                                  *|
        |*----------------------------------------------------------*/

        emit Transfer(ZERO_ADDRESS, _minter, _tokenId);

        /*----------------------------------------------------------*|
        |*  # SAFE TRANSFER                                         *|
        |*----------------------------------------------------------*/

        _owners.push(_to);

        emit Transfer(_minter, _to, _tokenId);

        require(_checkOnERC721Received(_minter, _to, _tokenId, _data));
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on
     * msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address _from, address _to, uint256 _tokenId) private {
        require(ownerOf(_tokenId) == _from);
        if (_to == ZERO_ADDRESS) revert();

        // Clear approvals _from the previous owner
        _approve(ZERO_ADDRESS, _tokenId);

        _owners[_tokenId] = _to;

        emit Transfer(_from, _to, _tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(address to, uint256 _tokenId) private {
        _tokenApprovals[_tokenId] = to;
        emit Approval(ownerOf(_tokenId), to, _tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits a {ApprovalForAll} event.
     */
    function _setApprovalForAll(address _owner, address _operator, bool _approved) private {
        if (_owner == _operator) revert();
        _operatorApprovals[_owner][_operator] = _approved;
        emit ApprovalForAll(_owner, _operator, _approved);
    }

    /**
     * @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 _to target address that will receive the tokens
     * @param _from address representing the previous owner of the given token
     * ID
     * @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
    )
        private
        returns (bool)
    {
        if (_to.code.length > 0) {
            return IERC721Receiver(_to).onERC721Received(
                msg.sender, // operator
                _from, // from
                _tokenId,
                _data
            ) == 0x150b7a02;
        }
        // IERC721Receiver.onERC721Received.selector,
        else {
            return true;
        }
    }

    /**
     * @dev WARNING this function SHOULD only be called by frontends due to
     * unbound loop
     * @dev public visibility as it is needed by
     */
    function balanceOf(address _owner) external view returns (uint256) {
        if (_owner == ZERO_ADDRESS) revert();
        uint256 count = 0;
        uint256 totalSupply = _owners.length;
        for (uint256 i; i < totalSupply; i++) {
            if (_owner == _owners[i]) count++;
        }
        return count;
    }
}

// File: contracts/IERC721Receiver.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 * @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);
}