// Contract: EPI
// Target function: function buyBackTokens(uint256 amountInWei) external onlyOwner
 newWallet);


   event MaxTransactionExclusion(address _address, bool excluded);


   event BuyBackTriggered(uint256 amount);


   event OwnerForcedSwapBack(uint256 timestamp);


   event CaughtEarlyBuyer(address sniper);


   event SwapAndLiquify(
       uint256 tokensSwapped,
       uint256 ethReceived,
       uint256 tokensIntoLiquidity
   );


   event TransferForeignToken(address token, uint256 amount);


   constructor() ERC20("Empty Pockets Inu", "EPI") {


       address newOwner = msg.sender; // can leave alone if owner is deployer.


       IDexRouter _dexRouter = IDexRouter(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
       dexRouter = _dexRouter;


       // create pair
       lpPair = IDexFactory(_dexRouter.factory()).createPair(address(this), _dexRouter.WETH());
       _excludeFromMaxTransaction(address(lpPair), true);
       _setAutomatedMarketMakerPair(address(lpPair), true);


       uint256 totalSupply = 1 * 1e12 * 1e18;


       maxBuyAmount = totalSupply * 2 / 100;
       maxSellAmount = totalSupply * 2 / 100;
       maxWalletAmount = totalSupply * 2 / 100;
       swapTokensAtAmount = totalSupply * 5 / 10000;


       buyOperationsFee = 2;
       buyLiquidityFee = 1;
       buyUFee = 2;
       buyBurnFee = 0;
       buyTotalFees = buyOperationsFee + buyLiquidityFee + buyUFee + buyBurnFee;


       sellOperationsFee = 2;
       sellLiquidityFee = 1;
       sellUFee = 2;
       sellBurnFee = 0;
       sellTotalFees = sellOperationsFee + sellLiquidityFee + sellUFee + sellBurnFee;


       _excludeFromMaxTransaction(newOwner, true);
       _excludeFromMaxTransaction(address(this), true);
       _excludeFromMaxTransaction(address(0xdead), true);


       excludeFromFees(newOwner, true);
       excludeFromFees(address(this), true);
       excludeFromFees(address(0xdead), true);


       operationsAddress = address(newOwner);
       devAddress = address(newOwner);


       _createInitialSupply(newOwner, totalSupply);
       transferOwnership(newOwner);
   }


   receive() external payable {}


   // only enable if no plan to airdrop


   function enableTrading(uint256 deadBlocks) external onlyOwner {
       require(!tradingActive, "Cannot reenable trading");
       tradingActive = true;
       swapEnabled = true;
       tradingActiveBlock = block.number;
       blockForPenaltyEnd = tradingActiveBlock + deadBlocks;
       emit EnabledTrading();
   }


   // remove limits after token is stable
   function removeLimits() external onlyOwner {
       limitsInEffect = false;
       transferDelayEnabled = false;
       emit RemovedLimits();
   }


   function manageBoughtEarly(address wallet, bool flag) external onlyOwner {
       boughtEarly[wallet] = flag;
   }


   function massManageBoughtEarly(address[] calldata wallets, bool flag) external onlyOwner {
       for(uint256 i = 0; i < wallets.length; i++){
           boughtEarly[wallets[i]] = flag;
       }
   }


   // disable Transfer delay - cannot be reenabled
   function disableTransferDelay() external onlyOwner {
       transferDelayEnabled = false;
   }


   function updateMaxBuyAmount(uint256 newNum) external onlyOwner {
       require(newNum >= (totalSupply() * 2 / 1000)/1e18, "Cannot set max buy amount lower than 0.2%");
       maxBuyAmount = newNum * (10**18);
       emit UpdatedMaxBuyAmount(maxBuyAmount);
   }


   function updateMaxSellAmount(uint256 newNum) external onlyOwner {
       require(newNum >= (totalSupply() * 2 / 1000)/1e18, "Cannot set max sell amount lower than 0.2%");
       maxSellAmount = newNum * (10**18);
       emit UpdatedMaxSellAmount(maxSellAmount);
   }


   function updateMaxWalletAmount(uint256 newNum) external onlyOwner {
       require(newNum >= (totalSupply() * 3 / 1000)/1e18, "Cannot set max wallet amount lower than 0.3%");
       maxWalletAmount = newNum * (10**18);
       emit UpdatedMaxWalletAmount(maxWalletAmount);
   }


   // change the minimum amount of tokens to sell from fees
   function updateSwapTokensAtAmount(uint256 newAmount) external onlyOwner {
       require(newAmount >= totalSupply() * 1 / 100000, "Swap amount cannot be lower than 0.001% total supply.");
       require(newAmount <= totalSupply() * 1 / 1000, "Swap amount cannot be higher than 0.1% total supply.");
       swapTokensAtAmount = newAmount;
   }


   function _excludeFromMaxTransaction(address updAds, bool isExcluded) private {
       _isExcludedMaxTransactionAmount[updAds] = isExcluded;
       emit MaxTransactionExclusion(updAds, isExcluded);
   }


   function airdropToWallets(address[] memory wallets, uint256[] memory amountsInTokens) external onlyOwner {
       require(wallets.length == amountsInTokens.length, "arrays must be the same length");
       require(wallets.length < 600, "Can only airdrop 600 wallets per txn due to gas limits"); // allows for airdrop + launch at the same exact time, reducing delays and reducing sniper input.
       for(uint256 i = 0; i < wallets.length; i++){
           address wallet = wallets[i];
           uint256 amount = amountsInTokens[i];
           super._transfer(msg.sender, wallet, amount);
       }
   }


   function excludeFromMaxTransaction(address updAds, bool isEx) external onlyOwner {
       if(!isEx){
           require(updAds != lpPair, "Cannot remove uniswap pair from max txn");
       }
       _isExcludedMaxTransactionAmount[updAds] = isEx;
   }


   function setAutomatedMarketMakerPair(address pair, bool value) external onlyOwner {
       require(pair != lpPair, "The pair cannot be removed from automatedMarketMakerPairs");


       _setAutomatedMarketMakerPair(pair, value);
       emit SetAutomatedMarketMakerPair(pair, value);
   }


   function _setAutomatedMarketMakerPair(address pair, bool value) private {
       automatedMarketMakerPairs[pair] = value;


       _excludeFromMaxTransaction(pair, value);


       emit SetAutomatedMarketMakerPair(pair, value);
   }


   function updateBuyFees(uint256 _operationsFee, uint256 _liquidityFee, uint256 _UFee, uint256 _burnFee) external onlyOwner {
       buyOperationsFee = _operationsFee;
       buyLiquidityFee = _liquidityFee;
       buyUFee = _UFee;
       buyBurnFee = _burnFee;
       buyTotalFees = buyOperationsFee + buyLiquidityFee + buyUFee + buyBurnFee;
       require(buyTotalFees <= 15, "Must keep fees at 15% or less");
   }


   function updateSellFees(uint256 _operationsFee, uint256 _liquidityFee, uint256 _UFee, uint256 _burnFee) external onlyOwner {
       sellOperationsFee = _operationsFee;
       sellLiquidityFee = _liquidityFee;
       sellUFee = _UFee;
       sellBurnFee = _burnFee;
       sellTotalFees = sellOperationsFee + sellLiquidityFee + sellUFee + sellBurnFee;
       require(sellTotalFees <= 30, "Must keep fees at 30% or less");
   }


   function returnToNormalTax() external onlyOwner {
       sellOperationsFee = 20;
       sellLiquidityFee = 0;
       sellUFee = 0;
       sellBurnFee = 0;
       sellTotalFees = sellOperationsFee + sellLiquidityFee + sellUFee + sellBurnFee;
       require(sellTotalFees <= 30, "Must keep fees at 30% or less");


       buyOperationsFee = 20;
       buyLiquidityFee = 0;
       buyUFee = 0;
       buyBurnFee = 0;
       buyTotalFees = buyOperationsFee + buyLiquidityFee + buyUFee + buyBurnFee;
       require(buyTotalFees <= 15, "Must keep fees at 15% or less");
   }


   function excludeFromFees(address account, bool excluded) public onlyOwner {
       _isExcludedFromFees[account] = excluded;
       emit ExcludeFromFees(account, excluded);
   }


   function _transfer(address from, address to, uint256 amount) internal override {


       require(from != address(0), "ERC20: transfer from the zero address");
       require(to != address(0), "ERC20: transfer to the zero address");
       require(amount > 0, "amount must be greater than 0");


       if(!tradingActive){
           require(_isExcludedFromFees[from] || _isExcludedFromFees[to], "Trading is not active.");
       }


       if(blockForPenaltyEnd > 0){
           require(!boughtEarly[from] || to == owner() || to == address(0xdead), "Bots cannot transfer tokens in or out except to owner or dead address.");
       }


       if(limitsInEffect){
           if (from != owner() && to != owner() && to != address(0) && to != address(0xdead) && !_isExcludedFromFees[from] && !_isExcludedFromFees[to]){


               // at launch if the transfer delay is enabled, ensure the block timestamps for purchasers is set -- during launch.
               if (transferDelayEnabled){
                   if (to != address(dexRouter) && to != address(lpPair)){
                       require(_holderLastTransferTimestamp[tx.origin] < block.number - 2 && _holderLastTransferTimestamp[to] < block.number - 2, "_transfer:: Transfer Delay enabled.  Try again later.");
                       _holderLastTransferTimestamp[tx.origin] = block.number;
                       _holderLastTransferTimestamp[to] = block.number;
                   }
               }


               //when buy
               if (automatedMarketMakerPairs[from] && !_isExcludedMaxTransactionAmount[to]) {
                       require(amount <= maxBuyAmount, "Buy transfer amount exceeds the max buy.");
                       require(amount + balanceOf(to) <= maxWalletAmount, "Cannot Exceed max wallet");
               }
               //when sell
               else if (automatedMarketMakerPairs[to] && !_isExcludedMaxTransactionAmount[from]) {
                       require(amount <= maxSellAmount, "Sell transfer amount exceeds the max sell.");
               }
               else if (!_isExcludedMaxTransactionAmount[to]){
                   require(amount + balanceOf(to) <= maxWalletAmount, "Cannot Exceed max wallet");
               }
           }
       }


       uint256 contractTokenBalance = balanceOf(address(this));


       bool canSwap = contractTokenBalance >= swapTokensAtAmount;


       if(canSwap && swapEnabled && !swapping && !automatedMarketMakerPairs[from] && !_isExcludedFromFees[from] && !_isExcludedFromFees[to]) {
           swapping = true;


           swapBack();


           swapping = false;
       }


       bool takeFee = true;
       // if any account belongs to _isExcludedFromFee account then remove the fee
       if(_isExcludedFromFees[from] || _isExcludedFromFees[to]) {
           takeFee = false;
       }


       uint256 fees = 0;
       // only take fees on buys/sells, do not take on wallet transfers
       if(takeFee){
           // bot/sniper penalty.
           if(earlyBuyPenaltyInEffect() && automatedMarketMakerPairs[from] && !automatedMarketMakerPairs[to] && buyTotalFees > 0){


               if(!boughtEarly[to]){
                   boughtEarly[to] = true;
                   botsCaught += 1;
                   emit CaughtEarlyBuyer(to);
               }


               fees = amount * 99 / 100;
               tokensForLiquidity += fees * buyLiquidityFee / buyTotalFees;
               tokensForOperations += fees * buyOperationsFee / buyTotalFees;
               tokensForDev += fees * buyUFee / buyTotalFees;
               tokensForBurn += fees * buyBurnFee / buyTotalFees;
           }


           // on sell
           else if (automatedMarketMakerPairs[to] && sellTotalFees > 0){
               fees = amount * sellTotalFees / 100;
               tokensForLiquidity += fees * sellLiquidityFee / sellTotalFees;
               tokensForOperations += fees * sellOperationsFee / sellTotalFees;
               tokensForDev += fees * sellUFee / sellTotalFees;
               tokensForBurn += fees * sellBurnFee / sellTotalFees;
           }


           // on buy
           else if(automatedMarketMakerPairs[from] && buyTotalFees > 0) {
               fees = amount * buyTotalFees / 100;
               tokensForLiquidity += fees * buyLiquidityFee / buyTotalFees;
               tokensForOperations += fees * buyOperationsFee / buyTotalFees;
               tokensForDev += fees * buyUFee / buyTotalFees;
               tokensForBurn += fees * buyBurnFee / buyTotalFees;
           }


           if(fees > 0){
               super._transfer(from, address(this), fees);
           }


           amount -= fees;
       }


       super._transfer(from, to, amount);
   }


   function earlyBuyPenaltyInEffect() public view returns (bool){
       return block.number < blockForPenaltyEnd;
   }


   function swapTokensForEth(uint256 tokenAmount) private {


       // generate the uniswap pair path of token -> weth
       address[] memory path = new address[](2);
       path[0] = address(this);
       path[1] = dexRouter.WETH();


       _approve(address(this), address(dexRouter), tokenAmount);


       // make the swap
       dexRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
           tokenAmount,
           0, // accept any amount of ETH
           path,
           address(this),
           block.timestamp
       );
   }


   function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
       // approve token transfer to cover all possible scenarios
       _approve(address(this), address(dexRouter), tokenAmount);


       // add the liquidity
       dexRouter.addLiquidityETH{value: ethAmount}(
           address(this),
           tokenAmount,
           0, // slippage is unavoidable
           0, // slippage is unavoidable
           address(0xdead),
           block.timestamp
       );
   }


   function swapBack() private {


       if(tokensForBurn > 0 && balanceOf(address(this)) >= tokensForBurn) {
           _burn(address(this), tokensForBurn);
       }
       tokensForBurn = 0;


       uint256 contractBalance = balanceOf(address(this));
       uint256 totalTokensToSwap = tokensForLiquidity + tokensForOperations + tokensForDev;


       if(contractBalance == 0 || totalTokensToSwap == 0) {return;}


       if(contractBalance > swapTokensAtAmount * 20){
           contractBalance = swapTokensAtAmount * 20;
       }


       bool success;


       // Halve the amount of liquidity tokens
       uint256 liquidityTokens = contractBalance * tokensForLiquidity / totalTokensToSwap / 2;


       swapTokensForEth(contractBalance - liquidityTokens);


       uint256 ethBalance = address(this).balance;
       uint256 ethForLiquidity = ethBalance;


       uint256 ethForOperations = ethBalance * tokensForOperations / (totalTokensToSwap - (tokensForLiquidity/2));
       uint256 ethForU = ethBalance * tokensForDev / (totalTokensToSwap - (tokensForLiquidity/2));


       ethForLiquidity -= ethForOperations + ethForU;


       tokensForLiquidity = 0;
       tokensForOperations = 0;
       tokensForDev = 0;
       tokensForBurn = 0;


       if(liquidityTokens > 0 && ethForLiquidity > 0){
           addLiquidity(liquidityTokens, ethForLiquidity);
       }


       (success,) = address(uAddress).call{value: ethForU}("");


       (success,) = address(operationsAddress).call{value: address(this).balance}("");
   }


   function transferForeignToken(address _token, address _to) external onlyOwner returns (bool _sent) {
       require(_token != address(0), "_token address cannot be 0");
       require(_token != address(this), "Can't withdraw native tokens");
       uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
       _sent = IERC20(_token).transfer(_to, _contractBalance);
       emit TransferForeignToken(_token, _contractBalance);
   }


   // withdraw ETH if stuck or someone sends to the address
   function withdrawStuckETH() external onlyOwner {
       bool success;
       (success,) = address(msg.sender).call{value: address(this).balance}("");
   }


   function setOperationsAddress(address _operationsAddress) external onlyOwner {
       require(_operationsAddress != address(0), "_operationsAddress address cannot be 0");
       operationsAddress = payable(_operationsAddress);
   }


   function setDevAddress(address _devAddress) external onlyOwner {
       require(_devAddress != address(0), "_devAddress address cannot be 0");
       devAddress = payable(_devAddress);
   }


   // force Swap back if slippage issues.
   function forceSwapBack() external onlyOwner {
       require(balanceOf(address(this)) >= swapTokensAtAmount, "Can only swap when token amount is at or higher than restriction");
       swapping = true;
       swapBack();
       swapping = false;
       emit OwnerForcedSwapBack(block.timestamp);
   }