// Contract: Claims
// Target function: function claimMultipleTokensFromMultipleMerkleTree(
-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

////// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)

/* pragma solidity ^0.8.0; */

/* import "../../utils/introspection/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 Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @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 be 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 tokenId
    ) 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 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 Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @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 Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);

    /**
     * @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;
}

////// src/Verify.sol
/* pragma solidity 0.8.11; */

/**
 * @title Verify
 * @dev Merkle root comparison function.
 */
library Verify {
    /// @dev Check if the computedHash == comparisonHash.
    /// @param comparisonHash The merkle root hash passed to the function.
    /// @param proof The proof provided by the user.
    /// @param leaf The generated hash.
    /// @return Whether the computedHash == comparisonHash.
    function doesComputedHashMatchMerkleRootHash(
        bytes32 comparisonHash,
        bytes32[] memory proof,
        bytes32 leaf
    ) internal pure returns (bool) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];

            if (computedHash < proofElement) {
                computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
            } else {
                computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
            }
        }
        return computedHash == comparisonHash;
    }
}

////// src/ClaimERC1155ERC721ERC20.sol
/* pragma solidity ^0.8.11; */

/* import "openzeppelin-solidity/contracts/token/ERC1155/IERC1155.sol"; */
/* import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; */
/* import "openzeppelin-solidity/contracts/token/ERC20/utils/SafeERC20.sol"; */
/* import "openzeppelin-solidity/contracts/token/ERC721/IERC721.sol"; */
/* import "./Verify.sol"; */

contract ClaimERC1155ERC721ERC20 {
    using SafeERC20 for IERC20;

    struct Claim {
        address to;
        ERC1155Claim[] erc1155;
        ERC721Claim[] erc721;
        ERC20Claim erc20;
        bytes32 salt;
    }

    struct ERC1155Claim {
        uint256[] ids;
        uint256[] values;
        address contractAddress;
    }

    struct ERC721Claim {
        uint256[] ids;
        address contractAddress;
    }

    struct ERC20Claim {
        uint256[] amounts;
        address[] contractAddresses;
    }

    /// @dev Emits when a successful claim occurs.
    /// @param to The destination address for the claimed ERC1155, ERC721 and ERC20 tokens.
    /// @param erc1155 The array of ERC1155Claim structs containing the ids, values and ERC1155 contract address.
    /// @param erc721 The array of ERC721Claim structs containing the ids and ERC721 contract address.
    /// @param erc20 The ERC20Claim struct containing the amounts and ERC20 contract addresses.
    /// @param merkleRoot The merkle root hash for the specific set of items being claimed.
    event ClaimedMultipleTokens(
        address to,
        ERC1155Claim[] erc1155,
        ERC721Claim[] erc721,
        ERC20Claim erc20,
        bytes32 merkleRoot
    );

    /// @dev Internal function used to claim multiple token types in one claim.
    /// @param merkleRoot The merkle root hash for the specific set of items being claimed.
    /// @param claim The claim struct containing the destination address, all items to be claimed and optional salt param.
    /// @param proof The proof provided by the user performing the claim function.
    function _claimERC1155ERC721ERC20(
        bytes32 merkleRoot,
        Claim memory claim,
        bytes32[] calldata proof
    ) internal {
        _checkValidity(merkleRoot, claim, proof);
        for (uint256 i = 0; i < claim.erc1155.length; i++) {
            require(claim.erc1155[i].ids.length == claim.erc1155[i].values.length, "CLAIM_INVALID_INPUT");
            _transferERC1155(claim.to, claim.erc1155[i].ids, claim.erc1155[i].values, claim.erc1155[i].contractAddress);
        }
        for (uint256 i = 0; i < claim.erc721.length; i++) {
            _transferERC721(claim.to, claim.erc721[i].ids, claim.erc721[i].contractAddress);
        }
        if (claim.erc20.amounts.length != 0) {
            require(claim.erc20.amounts.length == claim.erc20.contractAddresses.length, "CLAIM_INVALID_INPUT");
            _transferERC20(claim.to, claim.erc20.amounts, claim.erc20.contractAddresses);
        }
        emit ClaimedMultipleTokens(claim.to, claim.erc1155, claim.erc721, claim.erc20, merkleRoot);
    }

    /// @dev Private function used to check the validity of a specific claim.
    /// @param merkleRoot The merkle root hash for the specific set of items being claimed.
    /// @param claim The claim struct containing the destination address, all items to be claimed and optional salt param.
    /// @param proof The proof provided by the user performing the claim function.
    function _checkValidity(
        bytes32 merkleRoot,
        Claim memory claim,
        bytes32[] memory proof
    ) private pure {
        bytes32 leaf = _generateClaimHash(claim);
        require(Verify.doesComputedHashMatchMerkleRootHash(merkleRoot, proof, leaf), "CLAIM_INVALID");
    }

    /// @dev Private function used to generate a hash from an encoded claim.
    /// @param claim The claim struct.
    function _generateClaimHash(Claim memory claim) private pure returns (bytes32) {
        return keccak256(abi.encode(claim));
    }

    /// @dev Private function used to transfer the ERC1155 tokens specified in a specific claim.
    /// @param to The destination address for the claimed tokens.
    /// @param ids The array of ERC1155 ids.
    /// @param values The amount of ERC1155 tokens of each id to be transferred.
    /// @param contractAddress The ERC1155 token contract address.
    function _transferERC1155(
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        address contractAddress
    ) private {
        require(contractAddress != address(0), "CLAIM_INVALID_CONTRACT_ZERO_ADDRESS");
        IERC1155(contractAddress).safeBatchTransferFrom(address(this), to, ids, values, "");
    }

    /// @dev Private function used to transfer the ERC721tokens specified in a specific claim.
    /// @param to The destination address for the claimed tokens.
    /// @param ids The array of ERC721 ids.
    /// @param contractAddress The ERC721 token contract address.
    function _transferERC721(
        address to,
        uint256[] memory ids,
        address contractAddress
    ) private {
        require(contractAddress != address(0), "CLAIM_INVALID_CONTRACT_ZERO_ADDRESS");
        for(uint256 i = 0; i < ids.length; i ++) {
            uint256 id = ids[i];
            IERC721(contractAddress).safeTransferFrom(address(this), to, id);
        }
    }

    /// @dev Private function used to transfer the ERC20 tokens specified in a specific claim.
    /// @param to The destination address for the claimed tokens.
    /// @param amounts The array of amounts of ERC20 tokens to be transferred.
    /// @param contractAddresses The array of ERC20 token contract addresses.
    function _transferERC20(
        address to,
        uint256[] memory amounts,
        address[] memory contractAddresses
    ) private {
        for (uint256 i = 0; i < amounts.length; i++) {
            address erc20ContractAddress = contractAddresses[i];
            uint256 erc20Amount = amounts[i];
            require(erc20ContractAddress != address(0), "CLAIM_INVALID_CONTRACT_ZERO_ADDRESS");
            IERC20(erc20ContractAddress).safeTransfer(to, erc20Amount);
        }
    }
}

////// src/Claims.sol
/* pragma solidity ^0.8.11; */

/* import "openzeppelin-solidity/contracts/access/AccessControl.sol"; */
/* import "./ClaimERC1155ERC721ERC20.sol"; */

/// @title A Multi Claim contract that enables claims of user rewards in the form of ERC1155, ERC721 and / or ERC20 tokens
/// @notice This contract manages claims for multiple token types
contract Claims is AccessControl, ClaimERC1155ERC721ERC20 {
    bytes4 private constant ERC1155_RECEIVED = 0xf23a6e61;
    bytes4 private constant ERC1155_BATCH_RECEIVED = 0xbc197c81;
    bytes4 private constant ERC721_RECEIVED = 0x150b7a02;

    mapping(address => mapping(bytes32 => bool)) public claimed;
    mapping(bytes32 => uint256) internal _expiryTime;

    event NewGiveaway(bytes32 merkleRoot, uint256 expiryTime);

    constructor() {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function getExpiryTime(bytes32 merkleRoot) external view returns (uint) {
        return _expiryTime[merkleRoot];
    }

    /// @notice Function to add a new giveaway.
    /// @param merkleRoot The merkle root hash of the claim data.
    /// @param expiryTime The expiry time for the giveaway.
    function addNewGiveaway(bytes32 merkleRoot, uint256 expiryTime) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _expiryTime[merkleRoot] = expiryTime;
        emit NewGiveaway(merkleRoot, expiryTime);
    }