POL Price: $0.321738 (+2.28%)

Contract Diff Checker

Contract Name:
TokenSwapSpot

Contract Source Code:

File 1 of 1 : TokenSwapSpot

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

interface ITokenSwap {
    function swapToken(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint24 feeTier,
        address payable reseller
    ) external payable;
    function getPrice(address token1,address token2,uint256 feeTier) external view returns (uint256);
    function WETH() external view returns (address);
}

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function allowance(address owner,address spender) external view returns (uint);
    function approve(address spender, uint256 amount) external returns (bool);
}

interface IWETH {
    function deposit() external payable;
    function withdraw(uint wad) external;
}

contract TokenSwapSpot {
    // Owner of the contract
    address public owner;

    // Oracle address to call for order processing
    address public oracle;

    // Address of the TokenSwap contract (Uniswap swap handler)
    address public tokenSwapContract;
    ITokenSwap tokenSwap;
    address public WETH;

    uint public oracleFee = 0.02 ether;

    // Event for order placed
    event OrderPlaced(address indexed user, address tokenIn, address tokenOut, uint256 price, uint256 amountIn);

    // Event for swap triggered via oracle
    event OrderTriggered(address indexed user, address tokenIn, address tokenOut, uint256 price, uint256 amountIn);

    // Event for order cancellation
    event OrderCancelled(address indexed user, address tokenIn, address tokenOut, uint256 price, uint256 amountIn);

    // Struct representing a limit order
    struct Order {
        address user;
        uint256 priceIn;
        uint256 pairPriceId;
        uint256 amountIn; // tokenInAmount
        uint8 status; // 0=>open / 1=>withdrawed / 2=>cancelled
        uint256 createdAt;
        uint256 updatedAt;
    }
    uint256 public lastOrderId;
    mapping(uint256 => Order) public orders;
    mapping (address => uint[]) userOrderIds;
    mapping (address =>uint) public userOrderCount;

    struct PairPrice{
        address tokenIn;
        address tokenOut;
        uint256 price;
        uint256 totalAmountIn;
        uint256 amountOut; // if 0 its open, if more it's done
        uint256 totalWithdrawn;
    }
    uint256 public lastPairId;
    mapping(uint256 => PairPrice) public pairPrices;
    mapping(address => mapping(address => mapping(uint=>uint))) pairPriceIds;
    
    // Modifier to restrict functions to the contract owner
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the contract owner");
        _;
    }

    // Modifier to restrict functions to the oracle
    modifier onlyOracle() {
        require(msg.sender == oracle, "Not the oracle");
        _;
    }

    constructor(address _tokenSwapContract) {
        owner = msg.sender;
        oracle = msg.sender;
        tokenSwapContract = _tokenSwapContract;
        tokenSwap = ITokenSwap(tokenSwapContract);
        WETH = tokenSwap.WETH();
    }

    // Function to place a limit order
    function placeOrder(address tokenIn, address tokenOut, uint256 price, uint256 amountIn) external payable {
        require(msg.value >= oracleFee,"Not enough oracle fee");
        
        if(tokenIn==address(0)) tokenIn=WETH;
        if(tokenOut==address(0)) tokenOut=WETH;

        if (msg.value > oracleFee && tokenIn==address(WETH)) {
            tokenIn = WETH;
            amountIn = msg.value - oracleFee;
            IWETH wethContract = IWETH(WETH);
            wethContract.deposit{value: amountIn}();

            //transfer oracle fee to oracle address
            (bool sent, ) = oracle.call{value: oracleFee}("");
            require(sent, "Failed to send oracle fee");
        }else{
            require(amountIn > 0, "Must specify an amount to swap");

            //transfer token to contract
            IERC20 token = IERC20(tokenIn);
            token.transferFrom(msg.sender, address(this), amountIn);

            //transfer oracle fee to oracle address
            if(msg.value>0){
                (bool sent, ) = oracle.call{value: msg.value}("");
                require(sent, "Failed to send oracle fee");
            }
        }
        
        uint256 pairPriceId = getPairPriceId(tokenIn,tokenOut,price);
        pairPrices[pairPriceId].totalAmountIn += amountIn;

        //approve tokenIn
        IERC20 erc20 = IERC20(tokenIn);
        if(erc20.allowance(address(this), tokenSwapContract)<amountIn)
            erc20.approve(tokenSwapContract,type(uint256).max);

        //place order
        uint priceIn = tokenSwap.getPrice(tokenIn, tokenOut,3000);
        Order memory newOrder = Order({
            user: msg.sender,
            priceIn: priceIn,
            pairPriceId:pairPriceId,
            amountIn: amountIn,
            status:0,//pending
            createdAt: block.timestamp,
            updatedAt: 0
        });

        lastOrderId++;
        orders[lastOrderId] = newOrder;
        userOrderIds[msg.sender].push(lastOrderId);
        userOrderCount[msg.sender]++;

        emit OrderPlaced(msg.sender, tokenIn, tokenOut, price, amountIn);
    }

    // Function to cancel an order
    function cancelOrder(uint256 orderIndex) external {
        //validate inputs
        require(orderIndex<=userOrderCount[msg.sender],"Order id is not valid");
        uint256 orderId = userOrderIds[msg.sender][orderIndex];
        require(orders[orderId].status==0 && orders[orderId].amountIn>0,"Order is not open");

        //update order status
        orders[orderId].status=2;
        orders[orderId].updatedAt=block.timestamp;
        
        //rolback the amounts 
        uint256 pairPriceId = orders[orderId].pairPriceId;
        if(pairPrices[pairPriceId].tokenIn==WETH)
        {
            IWETH(WETH).withdraw(orders[orderId].amountIn);
            (bool success, ) = msg.sender.call{value: orders[orderId].amountIn}("");
            require(success, "Failed to send ETH");
        }else
            IERC20(pairPrices[pairPriceId].tokenIn).transfer(msg.sender, orders[orderId].amountIn);
            
        pairPrices[pairPriceId].totalAmountIn -= orders[orderId].amountIn;
    }

    function withdrawOrder(uint256 orderIndex) public{
        
        //validate inputs
        require(orderIndex<=userOrderCount[msg.sender],"Order id is not valid");
        uint256 orderId = userOrderIds[msg.sender][orderIndex];
        require(orders[orderId].status==0 && orders[orderId].amountIn>0,"Order is not open");

        //update order status
        orders[orderId].status=1; //withdrawed
        orders[orderId].updatedAt=block.timestamp;
        
        //pay the swapped amounts 
        uint256 pairPriceId = orders[orderId].pairPriceId;
        require(pairPrices[pairPriceId].amountOut>0,"Order is not placed yet.");
        uint256 totalAmountIn = pairPrices[pairPriceId].totalAmountIn;
        uint256 totalAmountOut = pairPrices[pairPriceId].amountOut;
        uint256 myAmountIn = orders[orderId].amountIn;
        uint256 ratio = myAmountIn * 10**18 / totalAmountIn;
        uint256 myAmountOut = totalAmountOut * ratio / 10**18;

        if(pairPrices[pairPriceId].tokenOut==WETH)
        {
            //IWETH(WETH).withdraw(myAmountOut); //already converted to ETH in swap contract
            (bool success, ) = msg.sender.call{value: myAmountOut}("");
            require(success, "Failed to send ETH");
        }else
            IERC20(pairPrices[pairPriceId].tokenOut).transfer(msg.sender, myAmountOut);
        pairPrices[pairPriceId].totalWithdrawn += myAmountOut;
    }

    // Function to process orders in steps (batch processing)
    function processPairPrice(uint256 pairPriceId) external onlyOracle {
        
        //TODO:
        //get price and check if it is ok
        //??????????????????

        //place swap order and get received token amount
        uint256 firstBalance;
        if(pairPrices[pairPriceId].tokenOut == WETH)
            firstBalance = address(this).balance;
        else
            firstBalance = IERC20(pairPrices[pairPriceId].tokenOut).balanceOf(address(this));

        tokenSwap.swapToken(
            pairPrices[pairPriceId].tokenIn,
            pairPrices[pairPriceId].tokenOut,
            pairPrices[pairPriceId].totalAmountIn,
            3000,
            payable(oracle)
        );

        uint256 newBalance;
        if(pairPrices[pairPriceId].tokenOut == WETH)
            newBalance = address(this).balance;
        else
            newBalance = IERC20(pairPrices[pairPriceId].tokenOut).balanceOf(address(this));
        uint256 income = newBalance - firstBalance;
        require(income>0,"Failed to process swap");

        //disable old pairPrice and update income
        pairPrices[pairPriceId].amountOut = income;
        pairPriceIds[pairPrices[pairPriceId].tokenIn][pairPrices[pairPriceId].tokenOut][pairPrices[pairPriceId].price]=0;
    }

    // Function to get all active orders for a user with pagination
    struct TmpOrder {
        uint userOrderIndex;
        address user;
        uint256 priceIn;
        uint256 pairPriceId;
        uint256 amountIn; // tokenInAmount
        uint8 status; // 0=>open / 1=>withdrawed / 2=>cancelled
        uint256 createdAt;
        uint256 updatedAt;
        address tokenIn;
        address tokenOut;
        uint256 price;
        uint256 totalAmountIn;
        uint256 amountOut; // if 0 its open, if more it's done
        uint256 totalWithdrawn;
    }
    function getUserOrdersPaginated(address user,uint256 fromId, uint256 toId) external view returns (TmpOrder[] memory) {
    
        if (toId > userOrderIds[user].length) {
            toId = userOrderIds[user].length;
        }
        require(fromId <= toId, "Invalid pagination range");

        uint256 size = toId - fromId;
        TmpOrder[] memory paginatedOrders = new TmpOrder[](size);
        
        for (uint256 i = 0; i < size; i++) {
            uint256 orderIndex = userOrderIds[user][fromId + i];
            uint256 pairPriceId = orders[orderIndex].pairPriceId;

            TmpOrder memory _tmpOrder = TmpOrder({
                userOrderIndex: fromId + i,
                user: orders[orderIndex].user,
                priceIn: orders[orderIndex].priceIn,
                pairPriceId: orders[orderIndex].pairPriceId,
                amountIn: orders[orderIndex].amountIn, // tokenInAmount
                status: orders[orderIndex].status, // 0=>open / 1=>withdrawed / 2=>cancelled
                createdAt: orders[orderIndex].createdAt,
                updatedAt: orders[orderIndex].updatedAt,
                tokenIn: pairPrices[pairPriceId].tokenIn,
                tokenOut: pairPrices[pairPriceId].tokenOut,
                price: pairPrices[pairPriceId].price,
                totalAmountIn: pairPrices[pairPriceId].totalAmountIn,
                amountOut: pairPrices[pairPriceId].amountOut,
                totalWithdrawn: pairPrices[pairPriceId].totalWithdrawn
            });

            // Store the struct in the paginatedOrders array
            paginatedOrders[i] = _tmpOrder;
        }

        return paginatedOrders;
    }

    
    // Function to set a new TokenSwap contract address
    function setTokenSwapContract(address _tokenSwapContract) external onlyOwner {
        tokenSwapContract = _tokenSwapContract;
        tokenSwap = ITokenSwap(tokenSwapContract);
        WETH = tokenSwap.WETH();
    }

    // Function to set the oracle address
    function setOracle(address _oracle) external onlyOwner {
        oracle = _oracle;
    }

    // Function to set the oracle address
    function setOracleFee(uint _oracleFee) external onlyOwner {
        oracleFee = _oracleFee;
    }

    // Helper function to round the price to a smaller step using base 2
    function roundPriceBase2(uint256 price) private pure returns (uint256) {
        uint256 magnitude = 1 << (log2(price) - 8); // Shift to create smaller rounding steps
        uint256 roundedPrice = (price / magnitude) * magnitude;
        return roundedPrice;
    }

    // Efficient log2 implementation (based on bit-shifting)
    function log2(uint256 x) private pure returns (uint256) {
        uint256 result = 0;
        if (x >= 2**128) { x >>= 128; result += 128; }
        if (x >= 2**64)  { x >>= 64; result += 64; }
        if (x >= 2**32)  { x >>= 32; result += 32; }
        if (x >= 2**16)  { x >>= 16; result += 16; }
        if (x >= 2**8)   { x >>= 8;  result += 8; }
        if (x >= 2**4)   { x >>= 4;  result += 4; }
        if (x >= 2**2)   { x >>= 2;  result += 2; }
        if (x >= 2**1)   { result += 1; }
        return result;
    }
    
    function getPairPriceId(address tokenIn,address tokenOut,uint256 price) private returns(uint){
        // Round the price to the nearest 0.1%
        price = roundPriceBase2(price);
        
        if(pairPriceIds[tokenIn][tokenOut][price]>0)
            return pairPriceIds[tokenIn][tokenOut][price];
        
        PairPrice memory _pairPrice = PairPrice({
            tokenIn:tokenIn,
            tokenOut:tokenOut,
            price:price,
            totalAmountIn:0,
            amountOut:0, // if 0 its open, if more it's done
            totalWithdrawn:0
        });
        lastPairId++;
        pairPrices[lastPairId] = _pairPrice;
        pairPriceIds[tokenIn][tokenOut][price] = lastPairId;
        return lastPairId;
    }

    // Emergency withdraw ETH
    function emergencyWithdrawETH(address payable to) external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ETH to withdraw");
        to.transfer(balance);
    }

    // Emergency withdraw ERC20 tokens
    function emergencyWithdrawToken(address token, address to) external onlyOwner {
        IERC20 erc20 = IERC20(token);
        uint256 balance = erc20.balanceOf(address(this));
        require(balance > 0, "No token balance to withdraw");
        erc20.transfer(to, balance);
    }

    // Fallback function to receive ETH
    receive() external payable {}
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):