// File: contracts/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;

interface IERC20 {
    function totalSupply() external view returns (uint256);

    function balanceOf(address _owner) external view returns (uint256);

    function transfer(address to, uint256 amount) external;

    function allowance(
        address from,
        address to,
        uint256 amount
    ) external view returns (bool);

    function approve(address sender, uint256 amount) external;

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external;

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed from, address indexed to, uint256 amount);
}
// File: contracts/interfaces/ISwapCentral.sol



// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.16;

interface ISwapCentral{
    function V2GetEstimatedOut(uint amountIn, address tokenIn, address tokenOut) external view returns (uint[] memory);

    function V2GetEstimatedIn(uint amountOutDesired, address tokenIn, address tokenOut) external view returns (uint[] memory);

    function V2SwapExactTokensForTokens(
        address caller,
        address tokenIn, 
        address tokenOut, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts);

    function V2SwapTokensForExactTokens(
        address caller, 
        address tokenIn,
        address tokenOut,
        uint amountOutDesired,
        uint amountInMax,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts);

    function V2SwapExactETHForTokens(
        address caller, 
        address tokenOut, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external payable returns (uint[] memory amounts);

    function V2SwapTokensForExactETH(
        address caller, 
        address tokenIn, 
        uint amountOut, 
        uint amountInMax,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts);

    function V2SwapExactTokensForETH(
        address caller, 
        address tokenIn, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts);

    function V2SwapETHForExactTokens(
        address caller, 
        address tokenOut, 
        uint amountOut,
        uint deadlineInSeconds
    ) external payable returns (uint[] memory amounts);

    function V2SwapExactTokensForTokensSupportingFeeOnTransferTokens(
        address caller, 
        address tokenIn, 
        address tokenOut, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external;

    function V2SwapExactETHForTokensSupportingFeeOnTransferTokens(
        address caller, 
        address tokenOut, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external payable;   

    function V2SwapExactTokensForETHSupportingFeeOnTransferTokens(
        address caller, 
        address tokenIn, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external;

    function V3SwapExactInputSingleHop(
        address caller, 
        address tokenIn, 
        address tokenOut,
        uint24 fee, 
        uint amountIn, 
        uint amountOutMin, 
        uint deadlineInSeconds,
        uint160 sqrtPriceLimitX96
    ) external returns (uint amountOut);

    function V3SwapExactOutputSingleHop(
        address caller, 
        address tokenIn, 
        address tokenOut, 
        uint24 fee, 
        uint amountOut, 
        uint amountInMax,
        uint deadlineInSeconds,
        uint160 sqrtPriceLimitX96
    ) external returns (uint amountIn); 
}


// File: contracts/libraries/TransferHelper.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.16;

import '../interfaces/IERC20.sol';

library TransferHelper {
    /// @notice Transfers tokens from the targeted address to the given destination
    /// @notice Errors with 'STF' if transfer fails
    /// @param token The contract address of the token to be transferred
    /// @param from The originating address from which the tokens will be transferred
    /// @param to The destination address of the transfer
    /// @param value The amount to be transferred
    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
    }

    /// @notice Transfers tokens from msg.sender to a recipient
    /// @dev Errors with ST if transfer fails
    /// @param token The contract address of the token which will be transferred
    /// @param to The recipient of the transfer
    /// @param value The value of the transfer
    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
    }

    /// @notice Approves the stipulated contract to spend the given allowance in the given token
    /// @dev Errors with 'SA' if transfer fails
    /// @param token The contract address of the token to be approved
    /// @param to The target of the approval
    /// @param value The amount of the given token the target will be allowed to spend
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
    }

    /// @notice Transfers ETH to the recipient address
    /// @dev Fails with `STE`
    /// @param to The destination of the transfer
    /// @param value The value to be transferred
    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'STE');
    }
}
// File: contracts/SwapConnector.sol



// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.16;

import "./interfaces/ISwapCentral.sol";
import "./libraries/TransferHelper.sol";

contract SwapConnector{
    ISwapCentral private swap;

    event NewOwner(address indexed newOwner, uint time);
    event WithdrawETH(uint amount, uint time);
    event WithdrawToken(address indexed token, uint amount, uint time);

    address public owner;

    modifier onlyOwner(){
        require(msg.sender == owner, "you're not the owner");
        _;
    }
    
    constructor(address swapCentral){
        swap = ISwapCentral(swapCentral);
        owner = msg.sender;
    }

    //---------------Uniswap V2 Starts Here----------------------//
    function V2GetEstimatedOut(uint amountIn, address tokenIn, address tokenOut) external view returns (uint[] memory){
        return swap.V2GetEstimatedOut(amountIn, tokenIn, tokenOut);
    }

    function V2GetEstimatedIn(uint amountOutDesired, address tokenIn, address tokenOut) external view returns (uint[] memory){
        return swap.V2GetEstimatedIn(amountOutDesired, tokenIn, tokenOut);
    }

    function V2SwapExactTokensForTokens(
        address tokenIn, 
        address tokenOut, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external
    {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
        TransferHelper.safeApprove(tokenIn, address(swap), amountIn);

        swap.V2SwapExactTokensForTokens(
            msg.sender,
            tokenIn, 
            tokenOut, 
            amountIn, 
            amountOutMin,
            deadlineInSeconds
        );
    }

    function V2SwapTokensForExactTokens(
        address tokenIn,
        address tokenOut,
        uint amountOutDesired,
        uint amountInMax,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts) {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountInMax);
        TransferHelper.safeApprove(tokenIn, address(swap), amountInMax);

        amounts = swap.V2SwapTokensForExactTokens(
            msg.sender, 
            tokenIn,
            tokenOut,
            amountOutDesired,
            amountInMax,
            deadlineInSeconds
        );
    }

    function V2SwapExactETHForTokens(
        address tokenOut, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external payable returns (uint[] memory amounts)
    {
        amounts = swap.V2SwapExactETHForTokens{value: msg.value}(
            msg.sender, 
            tokenOut, 
            amountOutMin,
            deadlineInSeconds
        );
    }

    function V2SwapTokensForExactETH(
        address tokenIn, 
        uint amountOut, 
        uint amountInMax,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts)
    {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountInMax);
        TransferHelper.safeApprove(tokenIn, address(swap), amountInMax);

        amounts = swap.V2SwapTokensForExactETH(
            msg.sender, 
            tokenIn, 
            amountOut, 
            amountInMax,
            deadlineInSeconds
        );
    }

    function V2SwapExactTokensForETH(
        address tokenIn, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts)
    {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
        TransferHelper.safeApprove(tokenIn, address(swap), amountIn);

        amounts = swap.V2SwapExactTokensForETH(
            msg.sender, 
            tokenIn, 
            amountIn, 
            amountOutMin,
            deadlineInSeconds
        );
    }

    function V2SwapETHForExactTokens(
        address tokenOut, 
        uint amountOut,
        uint deadlineInSeconds
    ) external payable returns (uint[] memory amounts)
    {
        amounts = swap.V2SwapETHForExactTokens{value: msg.value}(
        msg.sender, 
        tokenOut, 
        amountOut,
        deadlineInSeconds
        );
    } 

    function V2SwapExactTokensForTokensSupportingFeeOnTransferTokens(
        address tokenIn, 
        address tokenOut, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external 
    {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
        TransferHelper.safeApprove(tokenIn, address(swap), amountIn);

        swap.V2SwapExactTokensForTokensSupportingFeeOnTransferTokens(
            msg.sender, 
            tokenIn, 
            tokenOut, 
            amountIn, 
            amountOutMin,
            deadlineInSeconds
        );
    }    

    function V2SwapExactETHForTokensSupportingFeeOnTransferTokens(
        address tokenOut, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external payable
    {
        swap.V2SwapExactETHForTokensSupportingFeeOnTransferTokens{value: msg.value}(
            msg.sender, 
            tokenOut, 
            amountOutMin,
            deadlineInSeconds
        );
    }

    function V2SwapExactTokensForETHSupportingFeeOnTransferTokens(
        address tokenIn, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external
    {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
        TransferHelper.safeApprove(tokenIn, address(swap), amountIn);
        swap.V2SwapExactTokensForETHSupportingFeeOnTransferTokens(
            msg.sender,
            tokenIn,
            amountIn,
            amountOutMin,
            deadlineInSeconds
        );
    }


    //---------------Uniswap V3 Starts Here----------------------//
    function V3SwapExactInputSingleHop(
        address tokenIn, 
        address tokenOut, 
        uint24 fee, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds,
        uint160 sqrtPriceLimitX96
        ) external {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
        TransferHelper.safeApprove(tokenIn, address(swap), amountIn);

        swap.V3SwapExactInputSingleHop(
            msg.sender, 
            tokenIn, 
            tokenOut,
            fee,
            amountIn,
            amountOutMin,
            deadlineInSeconds,
            sqrtPriceLimitX96
        );
    }

    function V3SwapExactOutputSingleHop(
        address tokenIn, 
        address tokenOut, 
        uint24 fee,
        uint amountOut, 
        uint amountInMax,
        uint deadlineInSeconds,
        uint160 sqrtPriceLimitX96
        ) external {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountInMax);
        TransferHelper.safeApprove(tokenIn, address(swap), amountInMax);

        swap.V3SwapExactOutputSingleHop(
            msg.sender, 
            tokenIn, 
            tokenOut, 
            fee, 
            amountOut, 
            amountInMax, 
            deadlineInSeconds,
            sqrtPriceLimitX96
        );
    }

    //---------------Universal Getter Functions----------------------//
    
    function checkETHBalance() external view returns (uint) {
        return address(this).balance;
    }

    function checkTokenBalance(address token) external view returns (uint){
        return IERC20(token).balanceOf(address(this));
    }

    function withdrawETH() external onlyOwner {
        uint amount = address(this).balance;

        emit WithdrawETH(amount, block.timestamp);
        TransferHelper.safeTransferETH(owner, amount);  
    }

    function withdrawToken(address token) external onlyOwner {
        uint amount = IERC20(token).balanceOf(address(this));
    
        emit WithdrawToken(token, amount, block.timestamp);
        TransferHelper.safeTransfer(token, owner, amount);    
    }

    function changeOwner(address newOwner) external onlyOwner {      
        require(newOwner != address(0), "new candidate can't be address zero");
        owner = newOwner;

        emit NewOwner(newOwner, block.timestamp);
    }

    receive() external payable {}
    fallback() external payable {}
}