// Contract: dEaD
// Target function: function removeLiquidityETH(
 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero addy");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero addy");
        require(spender != address(0), "ERC20: approve to the zero addy");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

abstract contract ERC20Burnable is Context, ERC20 {
    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }
    function burnFrom(address account, uint256 amount) public virtual {
        _spendAllowance(account, _msgSender(), amount);
        _burn(account, amount);
    }
}

contract dEaD is ERC20Burnable, Ownable {
    using SafeMath for uint256;
    uint256 constant _initial_supply = 1 * (10**10) * (10**18); // 1 billion tokens, 18 decimals

    uint256 public tokensBurned;
    address payable public _developmentWallet;
    address payable public _buybackWallet;

    uint256 public maxWalletSize = _initial_supply.mul(2).div(100); // 2% of totalsupply
    uint256 public maxTransactionSize = _initial_supply.mul(1).div(100); // 1% of totalsupply
    uint256 public swapThreshold = _initial_supply.mul(2).div(1000); // .02% of totalsupply

    IUniswapV2Router02 public immutable uniswapV2Router;
    address public immutable uniswapV2Pair;

    bool inSwapAndLiquify;
    bool public swapAndLiquifyEnabled = true;

    uint256 liquidityLockTime = 0;

    mapping(address => bool) public _isExcludedFromFee;
    mapping(address => bool) public _isExcludedFromMaxTransaction;
    mapping(address => bool) public _isExcludedFromMaxWallet;

    enum FeeType {
        None,
        Buy,
        Sell
    }

    struct BuyFee {
        uint16 liquidity;
        uint16 dev;
        uint16 buyback;
    }

    struct SellFee {
        uint16 liquidity;
        uint16 dev;
        uint16 buyback;
    }

    BuyFee public buyFee;
    SellFee public sellFee;

    uint256 constant FEE_DENOM = 1000; // smallest fee unit is 0.1%

    event excludedFromFee(address account);
    event excludedFromMaxTransaction(address account);
    event excludedFromMaxWallet(address account);

    event includedInFee(address account);
    event includedInMaxTransaction(address account);
    event includedInMaxWallet(address account);

    event developmentWalletUpdated(address developmentWallet);
    event buybackWalletUpdated(address buybackWallet);
    event liquidityRemoved(uint256 amountToken, uint256 amountETH);

    event swapThresholdUpdated(uint256 minTokensBeforeSwap);
    event SwapAndLiquifyEnabledUpdated(bool enabled);
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiquidity
    );

    event MaxWalletSizeUpdated(uint256 maxWalletSize);
    event MaxTransactionSizeUpdated(uint256 maxTransactionSize);

    event LiquidityLockTimeUpdated(uint256 lockTime);

    modifier lockTheSwap() {
        inSwapAndLiquify = true;
        _;
        inSwapAndLiquify = false;
    }

    constructor() ERC20("0xdEaD", "0xdEaD") {
        // set fees to max
        setSellFee(150, 150, 0);
        setBuyFee(50, 50, 0);

        _developmentWallet = payable(msg.sender);
        _buybackWallet = payable(msg.sender);

        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        // Create a uniswap pair for this new token
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
        // set the rest of the contract variables
        uniswapV2Router = _uniswapV2Router;

        _mint(msg.sender, _initial_supply.mul(10).div(100)); // for liquidity pool
        _mint(0x000000000000000000000000000000000000dEaD, _initial_supply.mul(90).div(100)); // burned to dEaD

        _isExcludedFromFee[msg.sender] = true;
        _isExcludedFromMaxTransaction[msg.sender] = true;
        _isExcludedFromMaxWallet[msg.sender] = true;

        _isExcludedFromFee[address(this)] = true;
        _isExcludedFromMaxTransaction[address(this)] = true;
        _isExcludedFromMaxWallet[address(this)] = true;

        _isExcludedFromFee[_developmentWallet] = true;
        _isExcludedFromMaxTransaction[_developmentWallet] = true;
        _isExcludedFromMaxWallet[_developmentWallet] = true;

        _isExcludedFromFee[_buybackWallet] = true;
        _isExcludedFromMaxTransaction[_buybackWallet] = true;
        _isExcludedFromMaxWallet[_buybackWallet] = true;
    }

    //to receive ETH from uniswapV2Router when swapping
    receive() external payable {}

    // public functions

    function transfer(address to, uint256 amount)
        public
        override
        returns (bool)
    {
        address _owner = _msgSender();
        (bool takeFee, FeeType feeType) = checkFeeRequired(_owner, to);

        checkTransferAllowed(_owner, to, amount, takeFee);

        if (takeFee) {
            //check for swapAndLiquify available
            uint256 contractBalance = balanceOf(address(this));
            if (
                contractBalance >= swapThreshold &&
                !inSwapAndLiquify &&
                swapAndLiquifyEnabled &&
                _owner != uniswapV2Pair
            ) {
                //perform swapAndLiquify
                swapAndLiquify(swapThreshold);
            }

            uint256 fee = calculateFee(amount, feeType);
            _transfer(_owner, address(this), fee);
            _transfer(_owner, to, amount - fee);
        } else {
            _transfer(_owner, to, amount);
        }
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override returns (bool) {
        address spender = _msgSender();

        (bool takeFee, FeeType feeType) = checkFeeRequired(from, to);
        checkTransferAllowed(from, to, amount, takeFee);

        _spendAllowance(from, spender, amount);

        if (takeFee) {
            //check for swapAndLiquify available
            uint256 contractBalance = balanceOf(address(this));
            if (
                contractBalance >= swapThreshold &&
                !inSwapAndLiquify &&
                swapAndLiquifyEnabled &&
                from != uniswapV2Pair
            ) {
                //perform swapAndLiquify
                swapAndLiquify(swapThreshold);
            }

            uint256 fee = calculateFee(amount, feeType);
            _transfer(from, address(this), fee);
            _transfer(from, to, amount - fee);
        } else {
            _transfer(from, to, amount);
        }

        return true;
    }

    function burn(uint256 amount) public override {
        _burn(_msgSender(), amount);
        tokensBurned += amount;
    }

    function burnFrom(address account, uint256 amount) public override {
        _spendAllowance(account, _msgSender(), amount);
        _burn(account, amount);
        tokensBurned += amount;
    }

    function liquidityLockedUntil() external view returns (uint256) {
        return liquidityLockTime;
    }

    // internal functions

    function checkTransferAllowed(
        address from,
        address to,
        uint256 amount,
        bool takeFee
    ) internal view {
        if (to != uniswapV2Pair) {
            require(
                balanceOf(to) + amount < maxWalletSize ||
                    _isExcludedFromMaxWallet[to],
                "Exceeds receivers maximum wallet size"
            );
        }
        if (takeFee) {
            require(
                amount <= maxTransactionSize ||
                    (_isExcludedFromMaxTransaction[from] ||
                        _isExcludedFromMaxTransaction[to]),
                "Transaction larger than allowed"
            );
        }
    }

    function checkFeeRequired(address from, address to)
        internal
        view
        returns (bool, FeeType)
    {
        if (from == uniswapV2Pair && !_isExcludedFromFee[to]) {
            return (true, FeeType.Buy);
        } else if (to == uniswapV2Pair && !_isExcludedFromFee[from]) {
            return (true, FeeType.Sell);
        } else {
            return (false, FeeType.None);
        }
    }

    function calculateFee(uint256 amount, FeeType feeType)
        internal
        view
        returns (uint256 fee)
    {
        uint256 feePercentage = 0;
        if (feeType == FeeType.Buy) {
            feePercentage =
                buyFee.liquidity +
                buyFee.dev +
                buyFee.buyback;
        } else if (feeType == FeeType.Sell) {
            feePercentage =
                sellFee.liquidity +
                sellFee.dev +
                sellFee.buyback;
        }
        fee = (amount * feePercentage) / FEE_DENOM;
        return fee;
    }

    function swapAndLiquify(uint256 tokens) internal lockTheSwap {
        // split tokens by buy fee ratio
        uint256 feeDenominator = 
            buyFee.liquidity +
            buyFee.dev +
            buyFee.buyback;

        uint256 liquidityFee = (tokens * buyFee.liquidity) / feeDenominator;

        // sell tokens minus half of liquidity cut for eth
        swapTokensForEth(tokens - (liquidityFee / 2));

        // split resulting eth balance of contract by ratio, giving liquidity half weight
        uint256 contractEth = address(this).balance;

        uint256 ethDenominator = 
            (buyFee.liquidity / 2) +
            buyFee.dev +
            buyFee.buyback;

        uint256 liquidityEth = (contractEth * (buyFee.liquidity / 2)) /
            ethDenominator;
        uint256 buybackEth = (contractEth * buyFee.buyback) / ethDenominator;

        // provide liquidity with eth portion and remaining tokens

        if (liquidityEth > 0) {
            // Add liquidity to uniswap
            addLiquidity(liquidityFee / 2, liquidityEth);
        }

        _buybackWallet.transfer(buybackEth);

        // send all remaining eth to dev wallet (in case of rounding)
        _developmentWallet.transfer(address(this).balance); 

        emit SwapAndLiquify(tokens, contractEth, liquidityFee / 2);
    }

    function swapTokensForEth(uint256 tokenAmount) internal {
        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // make the swap
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }

    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) internal {
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // add the liquidity
        uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            address(this),
            block.timestamp
        );
    }

    // admin functions
    function setDevelopmentWallet(address development) external onlyOwner {
        require(development != address(0), "Development wallet cannot be 0x0");
        _developmentWallet = payable(development);
        emit developmentWalletUpdated(_developmentWallet);
    }

    function setBuybackWallet(address buyback) external onlyOwner {
        require(buyback != address(0), "Buyback wallet cannot be 0x0");
        _buybackWallet = payable(buyback);
        emit buybackWalletUpdated(_buybackWallet);
    }

    function setSellFee(
        uint16 liquidity,
        uint16 dev,
        uint16 buyback
    ) public onlyOwner {
        require(
            liquidity + dev + buyback < FEE_DENOM,
            "invalid fee structure"
        );

        sellFee.liquidity = liquidity;
        sellFee.dev = dev;
        sellFee.buyback = buyback;
    }

    function setBuyFee(
        uint16 liquidity,
        uint16 dev,
        uint16 buyback
    ) public onlyOwner {
        require(
            liquidity + dev + buyback < FEE_DENOM,
            "invalid fee structure"
        );

        buyFee.liquidity = liquidity;
        buyFee.dev = dev;
        buyFee.buyback = buyback;
    }

    function setSwapThreshold(uint256 newSwapThreshold) external onlyOwner {
        swapThreshold = newSwapThreshold;
        emit swapThresholdUpdated(newSwapThreshold);
    }

    function setMaxTransactionSize(uint256 maxTxSize) external onlyOwner {
        maxTransactionSize = maxTxSize;
        emit MaxTransactionSizeUpdated(maxTransactionSize);
    }

    function setMaxWalletSize(uint256 maxWallet) external onlyOwner {
        maxWalletSize = maxWallet;
        emit MaxWalletSizeUpdated(maxWalletSize);
    }

    function excludeFromFee(address account) external onlyOwner {
        require(
            !_isExcludedFromFee[account],
            "Account is already excluded from fee"
        );
        _isExcludedFromFee[account] = true;

        emit excludedFromFee(account);
    }

    function excludeFromMaxTransaction(address account) external onlyOwner {
        require(
            !_isExcludedFromMaxTransaction[account],
            "Account is already excluded from max transaction"
        );
        _isExcludedFromMaxTransaction[account] = true;

        emit excludedFromMaxTransaction(account);
    }

    function excludeFromMaxWallet(address account) external onlyOwner {
        require(
            !_isExcludedFromMaxWallet[account],
            "Account is already excluded from max wallet"
        );
        _isExcludedFromMaxWallet[account] = true;

        emit excludedFromMaxWallet(account);
    }

    function includeInFee(address account) external onlyOwner {
        require(
            _isExcludedFromFee[account],
            "Account is already included in fee"
        );
        _isExcludedFromFee[account] = false;

        emit includedInFee(account);
    }

    function includeInMaxTransaction(address account) external onlyOwner {
        require(
            _isExcludedFromMaxTransaction[account],
            "Account is already included in max transaction"
        );
        _isExcludedFromMaxTransaction[account] = false;

        emit includedInMaxTransaction(account);
    }

    function includeInMaxWallet(address account) external onlyOwner {
        require(
            _isExcludedFromMaxWallet[account],
            "Account is already included in max wallet"
        );
        _isExcludedFromMaxWallet[account] = false;

        emit includedInMaxWallet(account);
    }

    function updateLiquidityLock(uint256 lockTime) external onlyOwner {
        require(
            block.timestamp > liquidityLockTime,
            "New liquidity lock time must be after the current lock time"
        );
        liquidityLockTime = lockTime;
        emit LiquidityLockTimeUpdated(lockTime);
    }