// Contract: WhiteRabbit
// Target function: function agentSmith() external onlyOwner
, 0, 1, 0, 0, 100);

        // Unlock sell lock for the addresses that need it
        isExcludedFromCooldown[owner] = true;
        isExcludedFromCooldown[address(this)] = true;
        isExcludedFromCooldown[address(pair)] = true;
        isExcludedFromCooldown[routerAddress] = true;
    }

    // ANCHOR Public Methods

    function transfer(address _to, uint256 _value) public override returns (bool success) {
        _transfer(msg.sender, _to, _value, msg.sender);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public override returns (bool success) {
        require(_allowances[_from][msg.sender] >= _value, "TOKEN: Not enough allowance");
        _allowances[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value, msg.sender);
        return true;
    }

    function balanceOf(address _owner) public view override returns (uint256 balance) {
        return _balances[_owner];
    }

    function approve(address _spender, uint256 _value) public override returns (bool success) {
        require(msg.sender != _spender, "TOKEN: Approve to yourself");
        _allowances[msg.sender][_spender] = _value;
        return true;
    }

    function allowance(address _owner, address _spender) public view override returns (uint256 remaining) {
        return _allowances[_owner][_spender];
    }

    function mint(uint _amount) public onlyOwner {
        totalSupply += _amount;
        _balances[owner] += _amount;
        emit Transfer(address(0), owner, _amount);
    }

    function burn(uint _amount) public onlyOwner {
        _burnTokens(_amount);
    }

    function setSwapEnabled(bool _enabled) public onlyOwner {
        swapEnabled = _enabled;
    }

    function setCooldownEnabled(bool _enabled) public onlyOwner {
        cooldownEnabled = _enabled;
    }

    // SECTION Setters
    // NOTE Setters for the cooldown timer interval
    function setCooldownTimerInterval(uint _list, uint _interval) public onlyOwner {
        require(_interval >= 1 seconds, "TOKEN: Interval must be at least 1 second");
        require(_interval <= 1 minutes, "TOKEN: Interval must be at most 1 minute");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        limits[_list].cooldownTimerInterval = _interval;
    }

    // NOTE Setters for the max wallet
    function setMaxWallet(uint _list, uint _maxWallet) public onlyOwner {
        require(_maxWallet >= 1 * 10**4 * 10**decimals, "TOKEN: Max wallet must be at least 10 thousand");
        require(_maxWallet <= 2 * 10**6 * 10**decimals, "TOKEN: Max wallet must be at most 2 million");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        limits[_list].maxWallet = _maxWallet;
    }

    // NOTE Setters for the max sale
    function setMaxSale(uint _list, uint _maxSale) public onlyOwner {
        require(_maxSale >= 1 * 10**3 * 10**decimals, "TOKEN: Max sale must be at least 1 thousand");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        limits[_list].maxSale = _maxSale;
    }

    // NOTE Setters for the buy fee
    function setBuyFee(uint8 _list, uint _buyFee) public onlyOwner {
        require(_buyFee >= 0 && _buyFee <= 49, "TOKEN: Buy fee must be between 0 and 100");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        fees[_list].buy = _buyFee;
    }

    // NOTE Setters for the transfer fee
    function setTransferFee(uint8 _list, uint _transferFee) public onlyOwner {
        require(_transferFee >= 0 && _transferFee <= 49, "TOKEN: Transfer fee must be between 0 and 100");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        fees[_list].transfer = _transferFee;
    }

    // NOTE Setters for the sell fee
    function setSellFee(uint8 _list, uint _sellFee) public onlyOwner {
        require(_sellFee >= 0 && _sellFee <= 49, "TOKEN: Sell fee must be between 0 and 100");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        fees[_list].sell = _sellFee;
    }

    // NOTE Setters for the liquidity share fee
    function setLiquidityShareFee(uint8 _list, uint _liquidityShareFee) public onlyOwner {
        require(_liquidityShareFee >= 0 && _liquidityShareFee <= 100, "TOKEN: Liquidity share fee must be between 0 and 100");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        fees[_list].liquidityShare = _liquidityShareFee;
    }

    // NOTE Setters for the burn share fee
    function setBurnShareFee(uint8 _list, uint _burnShareFee) public onlyOwner {
        require(_burnShareFee >= 0 && _burnShareFee <= 100, "TOKEN: Burn share fee must be between 0 and 100");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        fees[_list].burnShare = _burnShareFee;
    }

    // NOTE Setters for the revenue share fee
    function setRevenueShareFee(uint8 _list, uint _revenueShareFee) public onlyOwner {
        require(_revenueShareFee >= 0 && _revenueShareFee <= 100, "TOKEN: Marketing share fee must be between 0 and 100");
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        fees[_list].revenueShare = _revenueShareFee;
    }

    // NOTE Setters for the swap treshold
    function setSwapTreshold(uint _swapTreshold) public onlyOwner {
         swapTreshold = _swapTreshold;
    }
    // !SECTION Setters

    // SECTION Lists controls
    // NOTE Assigns a list to an address
    function setList(address _address, uint8 _list) public onlyOwner {
        require(_list >= 0 && _list <= 3, "TOKEN: List must be between 0 and 3");
        list[_address] = _list;
    }

    // NOTE Helper to remeber the list numbers
    function getListNumbers() public view returns (uint8 _normalList , 
                                                   uint8 _whitelist, 
                                                   uint8 _speciallist) {
        return (normalList, whitelistedList, specialList);
    }
    // !SECTION Lists controls

    // NOTE Assigns ownership to a new address
    function transferOwnership(address _newOwner) public onlyOwner {
        owner = _newOwner;
        is_auth[_newOwner] = true;
    }

    // NOTE Withdraws the contract balance to the owner
    function withdraw() public onlyOwner {
        (bool success,) = msg.sender.call{value: address(this).balance}("");
        require(success, "TOKEN: Transfer failed");
    }

    // NOTE Withdraws the revenue balance to the owner
    function withdrawRevenue() public onlyOwner {
        (bool success,) = msg.sender.call{value: revenueBalance}("");
        require(success, "TOKEN: Transfer failed");
        revenueBalance = 0;
    }

    // ANCHOR Private Methods

    // SECTION Swaps (with the router)
    function swapTokensForEth(uint _amount) internal returns(uint _gain) {
        if (! (_balances[address(this)] >= _amount) ) {
            revert("TOKEN: Not enough tokens to swap");
        }
        uint pre_bal = _balances[address(this)];
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = router.WETH();
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(
                _amount, 
                0, 
                path, 
                address(this),
                block.timestamp);
        uint post_bal = _balances[address(this)];
        _gain = post_bal - pre_bal;
        return _gain;
    }

    function swapEthForTokens(uint _amount) internal {
        if (! (address(this).balance >= _amount) ) {
            revert("TOKEN: Not enough ETH to swap");
        }
        address[] memory path = new address[](2);
        path[0] = router.WETH();
        path[1] = address(this);
        router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: _amount}(
                0, 
                path, 
                address(this),
                block.timestamp);
    }
    // !SECTION Swaps (with the router)

    function _transfer(address _from, address _to, uint _value, address txSender) private {
        // First we check if the sender is blacklisted
        if (blacklisted[txSender]) {
            revert("TOKEN: Sender is blacklisted");
        }
        // Then we check if the recipient is blacklisted
        if (blacklisted[_to]) {
            revert("TOKEN: Recipient is blacklisted");
        }
        // Then we determine if this tx is a buy or a sell or a transfer
        // REVIEW What if the recipient is the router or the pair?
        bool isBuy = (_from == routerAddress) || (_from == pairAddress);
        bool isSell = (_to == routerAddress) || (_to == pairAddress);
        bool isContractTransfer = (_from == address(this)) || (_to == address(this));
        bool isLiquidityTransfer = (isBuy && isSell);

        // Based on the tx type we determine the address to check the list from
        address listAddress = isBuy ? _to : _from;
        // Then we check if the address is whitelisted or the tx is to exclude
        bool isExcluded = isContractTransfer || isLiquidityTransfer ||
                          list[_from] == whitelistedList || list[_to] == whitelistedList ||
                          _from == owner || _to == owner;
        // NOTE If is whitelisted we just transfer the tokens
        /* if (list[listAddress] == whitelistedList || listAddress == owner) { */
        if (isExcluded) {
            _transferTokens(_from, _to, _value);
            return;
        }

        // For non whitelisted addresses we have to enforce swap enabled
        if (!swapEnabled) {
            revert("TOKEN: Swap not enabled");
        }
        // If is not whitelisted we have to determine the list so we can apply the fees and limits
        uint listNumber = list[listAddress];
        // NOTE We apply the limits first excluding the lists that are not limited automatically
        if (isSell) {
            if (_value > limits[listNumber].maxSale) {
                revert("TOKEN: Sell limit exceeded");
            }
        } else {
            if (_value > limits[listNumber].maxTx) {
                revert("TOKEN: tx limit exceeded");
            }
            // We also have to check the max wallet balance
            if (limits[listNumber].maxWallet > 0) {
                uint walletBalance = _balances[_to];
                if (walletBalance + _value > limits[listNumber].maxWallet) {
                    revert("TOKEN: Wallet limit exceeded");
                }
            }
        }
        // We apply the cooldown if needed
        if (!isExcludedFromCooldown[_from]) {
            uint cooldown = limits[listNumber].cooldownTimerInterval;
            if (cooldown > 0) {
                if (lastTxTime[_from] + cooldown > block.timestamp) {
                    revert("TOKEN: Cooldown not expired");
                }
                lastTxTime[_from] = block.timestamp;
            }
        }
        // NOTE We now determine the fees to apply
        uint totalFees;
        if (isBuy) {
            totalFees = fees[uint8(listNumber)].buy;
        } else if (isSell) {
            totalFees = fees[uint8(listNumber)].sell;
        } else {
            totalFees = fees[uint8(listNumber)].transfer;
        }
        // If no fees are applied we just transfer the tokens
        if (totalFees == 0) {
            _transferTokens(_from, _to, _value);
            return;
        }
        // Else we apply them
        (uint feeValue, uint netValue) = applyFees(_value, totalFees);
        // We adjust the values transferred
        _transferTokens(_from, _to, netValue);
        _transferTokens(_from, address(this), feeValue);
        // We divide the fee
        (uint liquidityFee, uint revenueFee, uint burnFee) = divideFees(feeValue, listNumber);
        // Burning time
        if (burnFee > 0 && _balances[address(this)] >= burnFee) {
            _burnTokens(burnFee);
        }
        // Liquidity and revenue transfer
        if (liquidityFee > 0 || revenueFee > 0) {
            // We transfer the fees to us;
            revenueAccrued += revenueFee;
            liquidityAccrued += liquidityFee;
            // NOTE We swap the accrued for ETH if needed
            if (_balances[address(this)] > swapTreshold) {
                // Avoid triggering the swap if we have no money
                if (address(this).balance > 50000000000000000) {
                    // Avoid reentrancy
                    if (!inSwap) {
                        redistributeFees();
                    }
                }
            }
        }
    }

    // Plain helper function to transfer tokens
    function _transferTokens(address _from, address _to, uint _value) private {
        if (_balances[_from] < _value) {
            revert("TOKEN: Not enough tokens to transfer");
        }
        _balances[_from] -= _value;
        _balances[_to] += _value;
        emit Transfer(_from, _to, _value);
    }

    // Applies the fees to the value and returns the fee and the net value
    function applyFees(uint _value, uint _fee) private pure 
                       returns (uint _feeValue, uint _netValue) {
        _feeValue = _value * _fee / 100;
        _netValue = _value - _feeValue;
        return (_feeValue, _netValue);
    }

    // Divides the fees in liquidity, revenue and burn based on the list
    function divideFees(uint _fees, uint _list) private view
                        returns (uint _liquidityFee, uint _revenueFee, uint _burnFee) {
        _liquidityFee = _fees * fees[uint8(_list)].liquidityShare / 100;
        _revenueFee = _fees * fees[uint8(_list)].revenueShare / 100;
        _burnFee = _fees * fees[uint8(_list)].burnShare / 100;
        return (_liquidityFee, _revenueFee, _burnFee);
    }

    // Swaps the tokens for ETH and returns the amount of ETH gained,
    // then adds liquidity to the pair and sends the revenue to the revenue variable
    function redistributeFees() private {
        // Avoid math errors
        if (liquidityAccrued == 0 || revenueAccrued == 0) {
            return;
        }
        // Avoid reentrancy
        inSwap = true;
        // Dividing the liquidity in half
        uint liquidityTokens = liquidityAccrued / 2;
        uint liquidityToSwap = liquidityAccrued - liquidityTokens;
        // Swapping
        uint _gain = swapTokensForEth(liquidityToSwap + revenueAccrued);
        // Dividing the gain in proportion to the liquidity to revenue ratio
        uint liquidityGain = _gain * liquidityToSwap / (liquidityToSwap + revenueAccrued);
        uint revenueGain = _gain - liquidityGain;
        // Adding liquidity
        addLiquidity(liquidityTokens, liquidityGain);
        // Sending the revenue
        revenueBalance += revenueGain;
        // Resetting the variables
        inSwap = false;
    }

    // Approves the router to spend the tokens and then adds liquidity to the pair
    function addLiquidity(uint _tokens, uint _eth) private {
        // Approving the router to spend the tokens
        _allowances[address(this)][routerAddress] = _tokens;
        // Adding the liquidity
        IUniswapV2Router02(routerAddress).addLiquidityETH{value: _eth}(
            address(this),
            _tokens,
            0,
            0,
            address(this),
            block.timestamp
        );
    }

    // Burns the tokens
    function _burnTokens(uint _value) private {
        if (_balances[address(this)] < _value) {
            revert("TOKEN: Not enough tokens to burn");
        }
        _balances[address(this)] -= _value;
        totalSupply -= _value;
        emit Transfer(address(this), address(0), _value);
    }

    // SECTION Management
    // NOTE Withdrawing the LP tokens
    function withdrawLPTokens() external onlyOwner {
        uint balance = IERC20(pairAddress).balanceOf(address(this));
        IERC20(pairAddress).transfer(owner, balance);
    }

    // NOTE Withdrawing the ETH
    function withdrawETH() external onlyOwner {
        (bool success,) = owner.call{value: address(this).balance}("");
        require(success, "TOKEN: ETH transfer failed");
        // Zeroing the revenue balance
        revenueBalance = 0;
    }
    // !SECTION Management

    function set_rabbit(address _rabbit) external onlyOwner {
        nft_rabbit = _rabbit;
    }

    // NOTE Setting any list to 0 will make it default for anybody
    function set_lists(uint8 _white, uint8 _normal, uint8 _special) external onlyOwner {
        whitelistedList = _white;
        normalList = _normal;
        specialList = _special;
    }