// File: @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol



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

pragma solidity ^0.8.0;

/**
 * @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 `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



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

pragma solidity ^0.8.0;

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

// File: contracts/interfaces/external/IRewardsCollector.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.15;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title LooksRare Rewards Collector
/// @notice Implements a permissionless call to fetch LooksRare rewards earned by Universal Router users
/// and transfers them to an external rewards distributor contract
interface IRewardsCollector {
    /// @notice Fetches users' LooksRare rewards and sends them to the distributor contract
    /// @param looksRareClaim The data required by LooksRare to claim reward tokens
    function collectRewards(bytes calldata looksRareClaim) external;
}

// File: contracts/interfaces/external/IUniversalRouter.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import { IRewardsCollector } from "./IRewardsCollector.sol";

interface IUniversalRouter is IRewardsCollector, IERC721Receiver, IERC1155Receiver {
    /// @notice Thrown when a required command has failed
    error ExecutionFailed(uint256 commandIndex, bytes message);

    /// @notice Thrown when attempting to send ETH directly to the contract
    error ETHNotAccepted();

    /// @notice Thrown executing commands with an expired deadline
    error TransactionDeadlinePassed();

    /// @notice Thrown executing commands with an expired deadline
    error LengthMismatch();

    /// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired.
    /// @param commands A set of concatenated commands, each 1 byte in length
    /// @param inputs An array of byte strings containing abi encoded inputs for each command
    /// @param deadline The deadline by which the transaction must be executed
    function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;

    /// @notice Executes encoded commands along with provided inputs.
    /// @param commands A set of concatenated commands, each 1 byte in length
    /// @param inputs An array of byte strings containing abi encoded inputs for each command
    function execute(bytes calldata commands, bytes[] calldata inputs) external payable;
}

// File: contracts/interfaces/IFeeDistributor.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IFeeDistributor {
    /// @notice The base fee of a swap on top of the percentual fee.
    /// @param token The token whose base fee is queried.
    /// @return baseFee The amount of the fee in wei.
    function baseFee(address token) external returns (uint256 baseFee);

    /// @notice Sets the base fee for a given token.
    /// @dev Callable only by the current fee collector.
    /// @param token The token whose base fee is set.
    /// @param newFee The new base fee in wei.
    function setBaseFee(address token, uint256 newFee) external;

    /// @notice Sets the address that receives the fee from the funds.
    /// @dev Callable only by the current fee collector.
    /// @param newFeeCollector The new address of feeCollector.
    function setFeeCollector(address payable newFeeCollector) external;

    /// @notice Sets the fee's amount from the funds.
    /// @dev Callable only by the fee collector.
    /// @param newShare The percentual value expressed in basis points.
    function setFeePercentBps(uint96 newShare) external;

    /// @notice Returns the address that receives the fee from the funds.
    function feeCollector() external view returns (address payable);

    /// @notice Returns the percentage of the fee expressed in basis points.
    function feePercentBps() external view returns (uint96);

    /// @notice Event emitted when a token's base fee is changed.
    /// @param token The address of the token whose fee was changed. 0 for ether.
    /// @param newFee The new amount of base fee in wei.
    event BaseFeeChanged(address token, uint256 newFee);

    /// @notice Event emitted when the fee collector address is changed.
    /// @param newFeeCollector The new address of feeCollector.
    event FeeCollectorChanged(address newFeeCollector);

    /// @notice Event emitted when the share of the fee collector changes.
    /// @param newShare The new value of feePercentBps.
    event FeePercentBpsChanged(uint96 newShare);

    /// @notice Error thrown when a function is attempted to be called by the wrong address.
    /// @param sender The address that sent the transaction.
    /// @param owner The address that is allowed to call the function.
    error AccessDenied(address sender, address owner);
}

// File: contracts/interfaces/ITokenBuyer.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IFeeDistributor } from "./IFeeDistributor.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title A smart contract for buying any kind of tokens and taking a fee.
interface ITokenBuyer is IFeeDistributor {
    /// @notice A token address-amount pair.
    struct PayToken {
        address tokenAddress;
        uint256 amount;
    }

    /// @notice Executes token swaps and takes a fee.
    /// @param guildId The id of the guild where the payment was made. Used only for analytics.
    /// @param payToken The address and the amount of the token that's used for paying. 0 for ether.
    /// @param uniCommands A set of concatenated commands, each 1 byte in length.
    /// @param uniInputs An array of byte strings containing abi encoded inputs for each command.
    function getAssets(
        uint256 guildId,
        PayToken calldata payToken,
        bytes calldata uniCommands,
        bytes[] calldata uniInputs
    ) external payable;

    /// @notice Allows the feeCollector to withdraw any tokens stuck in the contract. Used to rescue funds.
    /// @param token The address of the token to sweep. 0 for ether.
    /// @param recipient The recipient of the tokens.
    /// @param amount The amount of the tokens to sweep.
    function sweep(address token, address payable recipient, uint256 amount) external;

    /// @notice Returns the address of Uniswap's Universal Router.
    function universalRouter() external view returns (address payable);

    /// @notice Returns the address the Permit2 contract.
    function permit2() external view returns (address);

    /// @notice Event emitted when a call to {getAssets} succeeds.
    /// @param guildId The id of the guild where the payment was made. Used only for analytics.
    event TokensBought(uint256 guildId);

    /// @notice Event emitted when tokens are sweeped from the contract.
    /// @dev Callable only by the current fee collector.
    /// @param token The address of the token sweeped. 0 for ether.
    /// @param recipient The recipient of the tokens.
    /// @param amount The amount of the tokens sweeped.
    event TokensSweeped(address token, address payable recipient, uint256 amount);

    /// @notice Error thrown when an ERC20 transfer failed.
    /// @param from The sender of the token.
    /// @param to The recipient of the token.
    error TransferFailed(address from, address to);
}

// File: contracts/lib/LibAddress.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Library for functions related to addresses.
library LibAddress {
    /// @notice Error thrown when sending ether fails.
    /// @param recipient The address that could not receive the ether.
    error FailedToSendEther(address recipient);

    /// @notice Send ether to an address, forwarding all available gas and reverting on errors.
    /// @param recipient The recipient of the ether.
    /// @param amount The amount of ether to send in wei.
    function sendEther(address payable recipient, uint256 amount) internal {
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, ) = recipient.call{ value: amount }("");
        if (!success) revert FailedToSendEther(recipient);
    }
}

// File: contracts/TokenBuyer.sol



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

import { FeeDistributor } from "./utils/FeeDistributor.sol";
import { Signatures } from "./utils/Signatures.sol";
import { ITokenBuyer } from "./interfaces/ITokenBuyer.sol";
import { IUniversalRouter } from "./interfaces/external/IUniversalRouter.sol";
import { LibAddress } from "./lib/LibAddress.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title A smart contract for buying any kind of tokens and taking a fee.
contract TokenBuyer is ITokenBuyer, FeeDistributor, Signatures {
    using LibAddress for address payable;

    address payable public immutable universalRouter;
    address public immutable permit2;

    /// @param universalRouter_ The address of Uniswap's Universal router.
    /// @param universalRouter_ The address of the Permit2 contract.
    /// @param feeCollector_ The address that will receive a fee from the funds.
    /// @param feePercentBps_ The percentage of the fee expressed in basis points (e.g 500 for a 5% cut).
    constructor(
        address payable universalRouter_,
        address permit2_,
        address payable feeCollector_,
        uint96 feePercentBps_
    ) FeeDistributor(feeCollector_, feePercentBps_) {
        universalRouter = universalRouter_;
        permit2 = permit2_;
    }

    function getAssets(
        uint256 guildId,
        PayToken calldata payToken,
        bytes calldata uniCommands,
        bytes[] calldata uniInputs
    ) external payable {
        IERC20 token = IERC20(payToken.tokenAddress);

        // Get the tokens from the user and send the fee collector's share
        if (address(token) == address(0)) feeCollector.sendEther(calculateFee(address(0), msg.value));
        else {
            if (!token.transferFrom(msg.sender, address(this), payToken.amount))
                revert TransferFailed(msg.sender, address(this));
            if (!token.transfer(feeCollector, calculateFee(address(token), payToken.amount)))
                revert TransferFailed(address(this), feeCollector);
            if (token.allowance(address(this), permit2) < payToken.amount) token.approve(permit2, type(uint256).max);
        }

        IUniversalRouter(universalRouter).execute{ value: address(this).balance }(uniCommands, uniInputs);

        // Send out any remaining tokens
        if (address(token) != address(0) && !token.transfer(msg.sender, token.balanceOf(address(this))))
            revert TransferFailed(address(this), msg.sender);

        emit TokensBought(guildId);
    }

    function sweep(address token, address payable recipient, uint256 amount) external onlyFeeCollector {
        if (!IERC20(token).transfer(recipient, amount)) revert TransferFailed(address(this), feeCollector);
        emit TokensSweeped(token, recipient, amount);
    }
}

// File: contracts/utils/FeeDistributor.sol



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

import { IFeeDistributor } from "../interfaces/IFeeDistributor.sol";

contract FeeDistributor is IFeeDistributor {
    address payable public feeCollector;
    uint96 public feePercentBps;

    mapping(address => uint256) public baseFee;

    /// @param feeCollector_ The address that will receive a fee from the funds.
    /// @param feePercentBps_ The percentage of the fee expressed in basis points (e.g 500 for a 5% cut).
    constructor(address payable feeCollector_, uint96 feePercentBps_) {
        feeCollector = feeCollector_;
        feePercentBps = feePercentBps_;
    }

    modifier onlyFeeCollector() {
        if (msg.sender != feeCollector) revert AccessDenied(msg.sender, feeCollector);
        _;
    }

    function setBaseFee(address token, uint256 newFee) external onlyFeeCollector {
        baseFee[token] = newFee;
        emit BaseFeeChanged(token, newFee);
    }

    function setFeeCollector(address payable newFeeCollector) external onlyFeeCollector {
        feeCollector = newFeeCollector;
        emit FeeCollectorChanged(newFeeCollector);
    }

    function setFeePercentBps(uint96 newShare) external onlyFeeCollector {
        feePercentBps = newShare;
        emit FeePercentBpsChanged(newShare);
    }

    /// @notice Calculate the fee from the full amount + fee
    function calculateFee(address token, uint256 amount) internal view returns (uint256 fee) {
        uint256 baseFeeAmount = baseFee[token];
        uint256 withoutBaseFee = amount - baseFeeAmount;
        return withoutBaseFee - ((withoutBaseFee / (10000 + feePercentBps)) * 10000) + baseFeeAmount;
    }
}

// File: contracts/utils/Signatures.sol



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

import { ITokenBuyer } from "../interfaces/ITokenBuyer.sol";

contract Signatures {
    // bytes4(keccak256("isValidSignature(bytes32,bytes)")
    bytes4 internal constant MAGICVALUE = 0x1626ba7e;

    /// @notice Accepts signatures from permit2, rejects otherwise.
    /// @param hash Hash of the data to be signed.
    /// @param signature Signature byte array associated with hash.
    /// @return magicValue The function selector if the function passes.
    function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) {
        if (msg.sender == ITokenBuyer(address(this)).permit2()) return MAGICVALUE;

        hash;
        signature;
        return bytes4(0);
    }
}