contract smart_dex is Protected {

    ////////////////// Basic definitions //////////////////

    address middleware;    

    event open_position(address token, uint limit_price, uint qty, bytes32 direction, bool executed);
    address public constant Dead = 0x000000000000000000000000000000000000dEaD;
    IUniswapFactory factory;
    IUniswapRouter02 router;

    mapping(address => uint) order_cooldown;
    uint cooldown_value;

    address aggregator = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
    AggregatorV2V3Interface usd_price;

    ////////////////// Structures and data //////////////////

    struct positions {
        address token;
        address actor;
        uint qty;
        uint price_limit;
        bytes32 action;
        bool active;
    }

    mapping(address => mapping(uint => positions)) actor_positions;
    mapping(uint => address) position_owner;
    mapping(address => uint[]) owned_positions;
    uint last_position;


    ////////////////// Constructor //////////////////

    constructor() {
        owner = msg.sender;
        router = IUniswapRouter02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        factory = IUniswapFactory(router.factory());
        usd_price = AggregatorV2V3Interface(aggregator);
    }

    ////////////////// Settings //////////////////

    function set_middleware(address addy) public onlyAuth {
        middleware = addy;
    }

    modifier onlyMiddleware() {
        require(msg.sender==middleware, "Unauthorized");
        _;
    }

    ////////////////// Public write functions //////////////////

    function set_limit_buy(address token, uint limit_price, uint qty) payable public safe { 
        require(order_cooldown[msg.sender] < block.timestamp, "Sheesh, calm down");       
        uint required_price = get_price(token, qty);
        uint fee = required_price/100;
        required_price = (required_price) + (fee);
        require(msg.value >= required_price);
        set_position(token, limit_price, msg.value, "limit_buy", msg.sender);
        order_cooldown[msg.sender] = block.timestamp + cooldown_value;
    }

    function set_limit_sell(address token, uint limit_price, uint qty) public safe {
        require(order_cooldown[msg.sender] < block.timestamp, "Sheesh, calm down");       
        ERC20 coin = ERC20(token);
        require(coin.balanceOf(msg.sender) >= qty, "Not enough tokens");
        require(coin.allowance(msg.sender, address(this)) >= qty, "Not enough allowance");
        coin.transferFrom(msg.sender, address(this), qty);
        set_position(token, limit_price, qty, "limit_sell", msg.sender);
        order_cooldown[msg.sender] = block.timestamp + cooldown_value;
    }

    function execute_buy(uint position) public onlyMiddleware {
        address actor = position_owner[position];
        address token = actor_positions[actor][position].token;
        uint qty = actor_positions[actor][position].qty;
        address[] memory path = new address[](2);
        path[0] = router.WETH();
        path[1] = token;
        router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: qty}(0, path, actor, block.timestamp);
        actor_positions[actor][position].active = false;
    }

    function execute_sell(uint position) public onlyMiddleware {
        address actor = position_owner[position];
        address token = actor_positions[actor][position].token;
        uint qty = actor_positions[actor][position].qty;
        address[] memory path = new address[](2);
        path[0] = token;
        path[1] = router.WETH();
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(qty, 0, path, actor, block.timestamp);  
        actor_positions[actor][position].active = false;      
    }

    function unstuck_native() public onlyAuth{
        (bool sent,) =msg.sender.call{value: (address(this).balance)}("");
        require(sent);
    }

    function unstuck_tokens(address tknAddress) public onlyAuth {
        ERC20 token = ERC20(tknAddress);
        uint256 ourBalance = token.balanceOf(address(this));
        require(ourBalance>0, "No tokens in our balance");
        token.transfer(msg.sender, ourBalance);
    }

    function harakiri() public onlyAuth {
        selfdestruct(payable(msg.sender));
    }

    ////////////////// Private write functions //////////////////

    function set_position(address token, uint limit_price, uint qty, bytes32 direction, address sender) private {
        last_position += 1;
        actor_positions[sender][last_position].token = token;
        actor_positions[sender][last_position].actor = sender;
        actor_positions[sender][last_position].action = "buy_limit";
        actor_positions[sender][last_position].qty = qty;
        actor_positions[sender][last_position].price_limit = limit_price;
        actor_positions[sender][last_position].active = true;
        owned_positions[sender].push(last_position);
        position_owner[last_position] = sender;
        emit open_position(token, limit_price, qty, direction, false);
    }


    ////////////////// Public view functions //////////////////
    
    // calculate price based on pair reserves (in ETH per 1 token)
   function get_price(address tkn, uint amount) public view returns(uint)
   {
    address pair = factory.getPair(router.WETH(), tkn);
    UniswapV2Pair pair_interface = UniswapV2Pair(pair);
    (uint Res0, uint Res1,) = pair_interface.getReserves();
    // decimals
    uint res0 = Res0*(10**ERC20(tkn).decimals());
    uint eth_answer = (amount*res0)/Res1;
    return(eth_answer); // return amount of token0 needed to buy token1
   }

   function price_in_dollars(uint eth_answer) public view returns(uint) {
    int current_usd = (usd_price.latestAnswer())/(10**8);
    uint usd_answer = (uint(current_usd) * eth_answer);
    return(usd_answer);
   }

    function get_positions(address actor) public view returns(uint[] memory) {
        return owned_positions[actor];
    }

    function get_single_position(uint pos) public view returns(positions memory) {
        address owner_of = position_owner[pos];
        return actor_positions[owner_of][pos];
    }
}
function set_limit_buy(address token, uint limit_price, uint qty) payable public safe { 
    require(order_cooldown[msg.sender] < block.timestamp, "Sheesh, calm down");       
    uint required_price = get_price(token, qty);
    uint fee = required_price/100;
    required_price = (required_price) + (fee);
    require(msg.value >= required_price);
    set_position(token, limit_price, msg.value, "limit_buy", msg.sender);
    order_cooldown[msg.sender] = block.timestamp + cooldown_value;
}

////////////////// Public write functions //////////////////