contract MerkleAirdrop is Ownable {
    using BitMaps for BitMaps.BitMap;

    address public immutable sender;
    IERC20 public immutable token;
    bytes32 public immutable merkleRoot;
    BitMaps.BitMap private claimed;

    event MerkleRootChanged(bytes32 merkleRoot);
    event Claim(address indexed claimant, uint256 amount);

    /**
     * @dev Constructor.
     * @param _sender The account to send airdrop tokens from.
     * @param _token The token contract to send tokens with.
     */
    constructor(address _sender, IERC20 _token, bytes32 _merkleRoot) {
        sender = _sender;
        token = _token;
        merkleRoot = _merkleRoot;
    }

    /**
     * @dev Claims airdropped tokens.
     * @param recipient The account being claimed for.
     * @param amount The amount of the claim being made.
     * @param merkleProof A merkle proof proving the claim is valid.
     */
    function claimTokens(address recipient, uint256 amount, bytes32[] calldata merkleProof) external {
        bytes32 leaf = keccak256(abi.encodePacked(recipient, amount));
        (bool valid, uint256 index) = MerkleProof.verify(merkleProof, merkleRoot, leaf);
        require(valid, "MerkleAirdrop: Valid proof required.");
        require(!isClaimed(index), "MerkleAirdrop: Tokens already claimed.");
        
        claimed.set(index);
        emit Claim(recipient, amount);

        token.transferFrom(sender, recipient, amount);
    }

    /**
     * @dev Returns true if the claim at the given index in the merkle tree has already been made.
     * @param index The index into the merkle tree.
     */
    function isClaimed(uint256 index) public view returns (bool) {
        return claimed.get(index);
    }
}
/**
 * @dev A contract to allow users to claim tokens via a 'merkle airdrop'.
 */
function isClaimed(uint256 index) public view returns (bool) {
    return claimed.get(index);
}

/**
 * @dev Returns true if the claim at the given index in the merkle tree has already been made.
 * @param index The index into the merkle tree.
 */