User: // SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";

struct StructUserAccount {
    address user;
}

contract SwapRouter is
    Initializable,
    PausableUpgradeable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(address initialOwner) public initializer {
        __Pausable_init();
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();
    }

    function getAmountsOut() external view returns (uint256[] memory amounts) {}

    receive() external payable {}

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}
}


can you complete the code, what i want is, user is able to swap multiple tokens at the same time using uniswap v2 and v3, whenever he swap a token from liquidity current price should be added with the token address pair address and with router address, so that we can calculate the profit, i want user should be able to swap or more specific can buy and sell at a certain percentage movement of price change like on every 5% of price up and down
Assistant: Creating a smart contract that facilitates multiple token swaps using both Uniswap V2 and V3, and also tracks price changes for strategic trading, requires careful design. Below is a sample code that outlines how you might implement such functionality:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";

contract SwapRouter is Initializable, PausableUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    using SafeERC20 for IERC20;

    IUniswapV2Router02 public uniswapV2Router;
    ISwapRouter public uniswapV3Router;

    struct TokenConfig {
        address token;
        address pair;
        uint256 lastPrice;
    }

    mapping(address => TokenConfig) public tokenConfigs;
    
    event TokenSwapped(address indexed user, address indexed token, uint256 amountIn, uint256 amountOut, uint256 price);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(address initialOwner, address _uniswapV2Router, address _uniswapV3Router) public initializer {
        __Pausable_init();
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();

        uniswapV2Router = IUniswapV2Router02(_uniswapV2Router);
        uniswapV3Router = ISwapRouter(_uniswapV3Router);
    }

    function configureToken(address token, address pair) external onlyOwner {
        require(token != address(0) && pair != address(0), "Invalid addresses");
        tokenConfigs[token] = TokenConfig(token, pair, 0);
    }

    function swapTokensV2(address tokenIn, uint256 amountIn, address tokenOut) external whenNotPaused {
        require(tokenConfigs[tokenIn].token != address(0), "Token not configured");
        
        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).safeApprove(address(uniswapV2Router), amountIn);

        address[] memory path = new address[](2);
        path[0] = tokenIn;
        path[1] = tokenOut;

        uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(
            amountIn,
            0,
            path,
            msg.sender,
            block.timestamp + 60
        );

        uint256 newPrice = calculateCurrentPrice(tokenIn, amounts[0]);
        emit TokenSwapped(msg.sender, tokenIn, amountIn, amounts[1], newPrice);
    }

    function swapTokensV3(address tokenIn, uint256 amountIn, address tokenOut, uint24 fee) external whenNotPaused {
        require(tokenConfigs[tokenIn].token != address(0), "Token not configured");

        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).safeApprove(address(uniswapV3Router), amountIn);

        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: fee,
                recipient: msg.sender,
                deadline: block.timestamp + 60,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        uniswapV3Router.exactInputSingle(params);
        
        uint256 newPrice = calculateCurrentPrice(tokenIn, amountIn); // Simplified
        emit TokenSwapped(msg.sender, tokenIn, amountIn, amountIn, newPrice);
    }

    function calculateCurrentPrice(address token, uint256 amount) internal view returns (uint256) {
        // Placeholder function to integrate with an oracle or use Off-chain calculation
        return amount; // dummy return
    }

    receive() external payable {}

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

```

### Key Components:

1. **Token Management:** The `configureToken` function allows you to set up tokens alongside their pair address (liquidity pool) and last known price.

2. **Swapping Logic:**
   - **Uniswap V2:** Swaps are done through `swapTokensV2` which uses Uniswap V2's router for token exchange.
   - **Uniswap V3:** Similarly, `swapTokensV3` facilitates swaps on Uniswap V3 using `exactInputSingle`.

3. **Tracking Price Changes:** A structure to store token pricing and pairs has been set up. The `calculateCurrentPrice` function should be extended to actually fetch and calculate real-time prices, potentially using a price oracle.

4. **Event Emission:** Emitting events after a swap (`TokenSwapped`) captures the token, amounts, and price metrics for external indexing or off-chain use.

5. **Upgradability:** Utilizes OpenZeppelin's upgradeable framework which allows you to deploy upgrades through the UUPS pattern.

### Considerations:
- The above logic does not directly buy/sell at defined price movements yet; you'd need additional logic to store price thresholds and trigger swaps based on those conditions.
- Real-world scenarios must include robust mechanisms to handle slippage, prevent reentrancy, ensure gas limits, and handle error states effectively.
User: there is also a view function that will will the current price of the token
Assistant: Certainly! To add a view function that retrieves the current price of a token, you'll need to have a mechanism in place to fetch live or historical prices. One common approach is to use an on-chain oracle like Chainlink to get price feeds, or you might implement it using Uniswap’s own functions to estimate prices based on current reserves.

Here's how you can extend the contract to include a function that estimates or fetches the current price:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";

contract SwapRouter is Initializable, PausableUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    using SafeERC20 for IERC20;

    IUniswapV2Router02 public uniswapV2Router;
    ISwapRouter public uniswapV3Router;

    struct TokenConfig {
        address token;
        address pair;
        uint256 lastPrice;
    }

    mapping(address => TokenConfig) public tokenConfigs;
    
    event TokenSwapped(address indexed user, address indexed token, uint256 amountIn, uint256 amountOut, uint256 price);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        address initialOwner, 
        address _uniswapV2Router, 
        address _uniswapV3Router
    ) public initializer {
        __Pausable_init();
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();

        uniswapV2Router = IUniswapV2Router02(_uniswapV2Router);
        uniswapV3Router = ISwapRouter(_uniswapV3Router);
    }

    function configureToken(address token, address pair) external onlyOwner {
        require(token != address(0) && pair != address(0), "Invalid addresses");
        tokenConfigs[token] = TokenConfig(token, pair, 0);
    }

    function swapTokensV2(address tokenIn, uint256 amountIn, address tokenOut) external whenNotPaused {
        require(tokenConfigs[tokenIn].token != address(0), "Token not configured");
        
        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).safeApprove(address(uniswapV2Router), amountIn);

        address[] memory path = new address[](2);
        path[0] = tokenIn;
        path[1] = tokenOut;

        uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(
            amountIn,
            0,
            path,
            msg.sender,
            block.timestamp + 60
        );

        uint256 newPrice = calculateCurrentPrice(tokenIn, amounts[0]);
        emit TokenSwapped(msg.sender, tokenIn, amountIn, amounts[1], newPrice);
    }

    function swapTokensV3(address tokenIn, uint256 amountIn, address tokenOut, uint24 fee) external whenNotPaused {
        require(tokenConfigs[tokenIn].token != address(0), "Token not configured");

        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).safeApprove(address(uniswapV3Router), amountIn);

        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: fee,
                recipient: msg.sender,
                deadline: block.timestamp + 60,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        uniswapV3Router.exactInputSingle(params);
        
        uint256 newPrice = calculateCurrentPrice(tokenIn, amountIn); // Simplified
        emit TokenSwapped(msg.sender, tokenIn, amountIn, amountIn, newPrice);
    }

    function calculateCurrentPrice(address token, uint256 amount) internal view returns (uint256) {
        // Placeholder function to integrate with an oracle or use Uniswap pool calculations
        return amount; // dummy return
    }

    function getCurrentTokenPrice(address token, address tokenOut, uint24 fee) public view returns (uint256 price) {
        // Logic to fetch current price from a Uniswap V3 pool or an oracle.
        // Using a simplified example with Uniswap V2 factory and reserves
        
        address pair = tokenConfigs[token].pair;
        require(pair != address(0), "Token pair not configured");

        // Fetch price based on liquidity pool reserves
        uint256 price = fetchPriceFromReserves(pair, token);
        
        return price;
    }
    
    function fetchPriceFromReserves(address pair, address token) internal view returns (uint256) {
        // Retrieve reserves and calculate price
        // This example uses Uniswap V2 pair reserve function for simplicity
        (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves();
        if (IUniswapV2Pair(pair).token0() == token) {
            return reserve1 * 1e18 / reserve0;
        } else {
            return reserve0 * 1e18 / reserve1;
        }
    }

    receive() external payable {}

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
```

### Explanation:

- **`getCurrentTokenPrice`:** This function is a view function that provides the current estimated price of a token based on the configured pair. It uses simplified logic that relies on the pool reserves to simulate price fetching. Replace with either an oracle or more accurate Uniswap price-fetching strategies depending on specific needs.

- **`fetchPriceFromReserves`:** A helper function to calculate the price using the reserves of the token pair. It's a very basic approximation and usually does not handle cases like low liquidity very well.

For high precision, you’d use off-chain services or on-chain oracles for fetching real-time prices in production-grade applications. The Uniswap V3 contracts offer advanced methods for fetching price information directly from the blockchain, which can be more technically complex
User: swapTokensV2 there is also a parameter to add uniswap router so that i can interact with multiple routers
Assistant: To allow interaction with multiple Uniswap V2 routers, you can modify the `swapTokensV2` function to accept the router address as a parameter. This flexibility lets you interact with various routers potentially deployed on different networks or forks.

Here's how you can modify the contract to accommodate this:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";

contract SwapRouter is Initializable, PausableUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    using SafeERC20 for IERC20;

    ISwapRouter public uniswapV3Router;

    struct TokenConfig {
        address token;
        address pair;
        uint256 lastPrice;
    }

    mapping(address => TokenConfig) public tokenConfigs;
    
    event TokenSwapped(address indexed user, address indexed token, uint256 amountIn, uint256 amountOut, uint256 price);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        address initialOwner,
        address _uniswapV3Router
    ) public initializer {
        __Pausable_init();
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();

        uniswapV3Router = ISwapRouter(_uniswapV3Router);
    }

    function configureToken(address token, address pair) external onlyOwner {
        require(token != address(0) && pair != address(0), "Invalid addresses");
        tokenConfigs[token] = TokenConfig(token, pair, 0);
    }

    function swapTokensV2(
        address router,
        address tokenIn,
        uint256 amountIn,
        address tokenOut
    ) external whenNotPaused {
        require(tokenConfigs[tokenIn].token != address(0), "Token not configured");

        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).safeApprove(router, amountIn);

        IUniswapV2Router02 uniswapV2Router = IUniswapV2Router02(router);

        address[] memory path = new address[](2);
        path[0] = tokenIn;
        path[1] = tokenOut;

        uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(
            amountIn,
            0,
            path,
            msg.sender,
            block.timestamp + 60
        );

        uint256 newPrice = calculateCurrentPrice(tokenIn, amounts[0]);
        emit TokenSwapped(msg.sender, tokenIn, amountIn, amounts[1], newPrice);
    }

    function swapTokensV3(address tokenIn, uint256 amountIn, address tokenOut, uint24 fee) external whenNotPaused {
        require(tokenConfigs[tokenIn].token != address(0), "Token not configured");

        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).safeApprove(address(uniswapV3Router), amountIn);

        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: fee,
                recipient: msg.sender,
                deadline: block.timestamp + 60,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        uniswapV3Router.exactInputSingle(params);

        uint256 newPrice = calculateCurrentPrice(tokenIn, amountIn);
        emit TokenSwapped(msg.sender, tokenIn, amountIn, amountIn, newPrice);
    }

    function calculateCurrentPrice(address token, uint256 amount) internal view returns (uint256) {
        // Placeholder function for real price calculation.
        return amount;
    }

    function getCurrentTokenPrice(address token, address tokenOut, uint24 fee) public view returns (uint256 price) {
        address pair = tokenConfigs[token].pair;
        require(pair != address(0), "Token pair not configured");

        uint256 price = fetchPriceFromReserves(pair, token);

        return price;
    }

    function fetchPriceFromReserves(address pair, address token) internal view returns (uint256) {
        (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves();
        if (IUniswapV2Pair(pair).token0() == token) {
            return reserve1 * 1e18 / reserve0;
        } else {
            return reserve0 * 1e18 / reserve1;
        }
    }

    receive() external payable {}

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
```

### Explanation:

- **Router Parameter in `swapTokensV2`:** The function now takes an additional `router` parameter, allowing it to interact with any specified Uniswap V2 router.

- **Dynamic Routing:** At the start of the `swapTokensV2` function, an instance of `IUniswapV2Router02` is created using the specified `router` address. This allows the function to dynamically handle swaps through any Uniswap V2 compatible router.

By allowing a dynamic router parameter, users can now execute swaps on different routers, potentially exploiting arbitrage opportunities or specific features of different Uniswap forks or deployments.
User: there may be scenario when some tokens left those should be returned to the user
Assistant: To handle scenarios where some tokens are left over after a swap, you can add logic at the end of both the `swapTokensV2` and `swapTokensV3` functions to return any remaining tokens to the user. This could happen if a swap doesn't utilize the full amount due to slippage or other reasons.

Here's how you can implement this:

```solidity
function swapTokensV2(
    address router,
    address tokenIn,
    uint256 amountIn,
    address tokenOut
) external whenNotPaused {
    require(tokenConfigs[tokenIn].token != address(0), "Token not configured");

    IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
    IERC20(tokenIn).safeApprove(router, amountIn);

    IUniswapV2Router02 uniswapV2Router = IUniswapV2Router02(router);

    address[] memory path = new address[](2);
    path[0] = tokenIn;
    path[1] = tokenOut;

    uint256 balanceBefore = IERC20(tokenIn).balanceOf(address(this));

    uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(
        amountIn,
        0,
        path,
        msg.sender,
        block.timestamp + 60
    );

    uint256 balanceAfter = IERC20(tokenIn).balanceOf(address(this));
    uint256 leftover = balanceAfter - balanceBefore;

    if (leftover > 0) {
        IERC20(tokenIn).safeTransfer(msg.sender, leftover);
    }

    uint256 newPrice = calculateCurrentPrice(tokenIn, amounts[0]);
    emit TokenSwapped(msg.sender, tokenIn, amountIn, amounts[1], newPrice);
}

function swapTokensV3(address tokenIn, uint256 amountIn, address tokenOut, uint24 fee) external whenNotPaused {
    require(tokenConfigs[tokenIn].token != address(0), "Token not configured");

    IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
    IERC20(tokenIn).safeApprove(address(uniswapV3Router), amountIn);

    uint256 balanceBefore = IERC20(tokenIn).balanceOf(address(this));

    ISwapRouter.ExactInputSingleParams memory params =
        ISwapRouter.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: tokenOut,
            fee: fee,
            recipient: msg.sender,
            deadline: block.timestamp + 60,
            amountIn: amountIn,
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0
        });

    uniswapV3Router.exactInputSingle(params);

    uint256 balanceAfter = IERC20(tokenIn).balanceOf(address(this));
    uint256 leftover = balanceAfter - balanceBefore;

    if (leftover > 0) {
        IERC20(tokenIn).safeTransfer(msg.sender, leftover);
    }

    uint256 newPrice = calculateCurrentPrice(tokenIn, amountIn);
    emit TokenSwapped(msg.sender, tokenIn, amountIn, amountIn, newPrice);
}
```

### Explanation:

1. **Balance Checks:**
   - Before executing a swap, the contract checks the balance of the swapped token.
   - After the swap, it checks the balance again to see if there are any leftover tokens.

2. **Return Leftover Tokens:**
   - If any tokens are left over after the swap (i.e., the balance is higher than expected), those tokens are transferred back to the user's wallet.

3. **Safe Transfers:**
   - The use of `SafeERC20.safeTransfer` ensures that token transfers handle any potential issues, such as checking return values, to prevent any errors and enhance security.

This approach ensures that users receive any unused tokens back in case of a swap that doesn't consume the full input amount, maintaining fairness and optimizing user experience.
User: please code the full code
Assistant: