// 

// SPDX-License-Identifier: MIT



pragma solidity ^0.8.22;



//GAS OPTIMIZED ERROR MESSAGES

error ERC20InvalidSender(address sender);

error ERC20InvalidReceiver(address receiver);

error ERC20InvalidApprover(address approver);

error ERC20InvalidSpender(address spender);

error ERC20TransferFailed();

error ERC20ZeroTransfer();

error PaymentFailed();





abstract contract Context {

  function _msgSender() internal view virtual returns (address) {

    return msg.sender;

  }

}



interface IERC20 {

  function totalSupply() external view returns (uint256);

  function balanceOf(address _account) external view returns (uint256);

  function transfer(address recipient, uint256 amount)

    external

    returns (bool);

  function allowance(address owner, address spender)

    external

    view

    returns (uint256);

  function approve(address spender, uint256 amount) external returns (bool);

  function transferFrom(

    address sender,

    address recipient,

    uint256 amount

  ) external returns (bool);

  event Transfer(address indexed from, address indexed to, uint256 value);

  event Approval(

    address indexed owner,

    address indexed spender,

    uint256 value

  );

}



abstract contract Ownable is Context {

  address private _owner;



  event OwnershipTransferred(

    address indexed previousOwner,

    address indexed newOwner

  );



  /**

   * @dev Initializes the contract setting the deployer as the initial owner.

   */

  constructor() {

    _setOwner(_msgSender());

  }



  /**

   * @dev Returns the address of the current owner.

   */

  function owner() public view virtual returns (address) {

    return _owner;

  }



  /**

   * @dev Throws if called by any _account other than the owner.

   */

  modifier onlyOwner() {

    require(owner() == _msgSender(), "Ownable: caller is not the owner");

    _;

  }



  function renounceOwnership() public virtual onlyOwner {

    _setOwner(address(0));

  }



  function transferOwnership(address newOwner) public virtual onlyOwner {

    require(

      newOwner != address(0),

      "Ownable: new owner is the zero address"

    );

    _setOwner(newOwner);

  }



  function _setOwner(address newOwner) private {

    address oldOwner = _owner;

    _owner = newOwner;

    emit OwnershipTransferred(oldOwner, newOwner);

  }

}



library SafeMath {



  function add(uint256 a, uint256 b) internal pure returns (uint256) {

    uint256 c = a + b;

    require(c >= a, "SafeMath: addition overflow");



    return c;

  }



  function sub(uint256 a, uint256 b) internal pure returns (uint256) {

    return sub(a, b, "SafeMath: subtraction overflow");

  }



  function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {

    require(b <= a, errorMessage);

    uint256 c = a - b;



    return c;

  }



  function mul(uint256 a, uint256 b) internal pure returns (uint256) {

    if (a == 0) {

      return 0;

    }



    uint256 c = a * b;

    require(c / a == b, "SafeMath: multiplication overflow");



    return c;

  }



  function div(uint256 a, uint256 b) internal pure returns (uint256) {

    return div(a, b, "SafeMath: division by zero");

  }



  function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {

    require(b > 0, errorMessage);

    uint256 c = a / b;

    // assert(a == b * c + a % b); // There is no case in which this doesn't hold



    return c;

  }



  function mod(uint256 a, uint256 b) internal pure returns (uint256) {

    return mod(a, b, "SafeMath: modulo by zero");

  }



  function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {

    require(b != 0, errorMessage);

    return a % b;

  }

}



interface IDexSwapFactory {

  function createPair(address tokenA, address tokenB) external returns (address pair);

}



interface IDexSwapRouter {

  function factory() external pure returns (address);

  function WETH() external pure returns (address);

  function swapExactTokensForETHSupportingFeeOnTransferTokens(

    uint amountIn,

    uint amountOutMin,

    address[] calldata path,

    address to,

    uint deadline

  ) external;

}



contract GrimesGrok is Context, IERC20, Ownable {



  using SafeMath for uint256;



  string private _name = "GrimesGrok";

  string private _symbol = "GGROK";

  uint8 private _decimals = 9; 



  uint256 public buyTax;

  uint256 public sellTax;

   

  mapping (address => uint256) _balances;

  mapping (address => mapping (address => uint256)) private _allowances;



  mapping (address => bool) public isExcludedFromFee;

  mapping (address => bool) public isMarketPair;

  mapping (address => bool) public isWalletLimitExempt;

  mapping (address => bool) public isTxLimitExempt;

  mapping (address => bool) public isWlAddress;



  uint256 private _totalSupply = 1_000_000_000 * 10**_decimals;



  uint256 public _maxTxAmount = _totalSupply.mul(1).div(100);   

  uint256 public _walletMax = _totalSupply.mul(1).div(100);    



  uint256 public swapThreshold = _totalSupply.mul(1).div(100);



  address public marketingWallet;



  bool public swapEnabled = true;

  bool public swapbylimit = true;

  bool public EnableTxLimit = true;

  bool public EnableWalletLimit = true;



  IDexSwapRouter public dexRouter;

  address public dexPair;



  bool public tradingEnable; 

  bool public transferSniperProtection;



  bool inSwap;



  modifier swapping() {

    inSwap = true;

    _;

    inSwap = false;

  }

   

  event SwapTokensForETH(

    uint256 amountIn,

    address[] path

  );



  constructor() {



    marketingWallet = msg.sender;



    IDexSwapRouter _dexRouter = IDexSwapRouter(

      0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D

    );



    dexPair = IDexSwapFactory(_dexRouter.factory())

      .createPair(address(this), _dexRouter.WETH());



    dexRouter = _dexRouter;

     

    isExcludedFromFee[address(this)] = true;

    isExcludedFromFee[msg.sender] = true;



    isWalletLimitExempt[msg.sender] = true;

    isWalletLimitExempt[address(this)] = true;

    isWalletLimitExempt[address(0xdead)] = true;

    isWalletLimitExempt[address(dexPair)] = true;



    isWlAddress[address(msg.sender)] = true;

    isWlAddress[address(this)] = true;

     

    isTxLimitExempt[address(0xdead)] = true;

    isTxLimitExempt[msg.sender] = true;

    isTxLimitExempt[address(this)] = true;



    isMarketPair[address(dexPair)] = true;



    buyTax = 30;

    sellTax = 30;

    transferSniperProtection = true;



    _balances[msg.sender] = _totalSupply;

    emit Transfer(address(0), msg.sender, _totalSupply);

  }



  // BoredFrogsERC-721



  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];   

  }



  function allowance(address owner, address spender) public view override returns (uint256) {

    return _allowances[owner][spender];

  }



  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 {

    if (owner == address(0)) {

      revert ERC20InvalidApprover(address(0));

    }

    if (spender == address(0)) {

      revert ERC20InvalidSpender(address(0));

    }



    _allowances[owner][spender] = amount;

    emit Approval(owner, spender, amount);

  }



   //to recieve ETH from Router when swaping

  receive() external payable {}



  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: Exceeds allowance"));

    return true;

  }



  function _transfer(address sender, address recipient, uint256 amount) private returns (bool) {



    if (sender == address(0)) {

      revert ERC20InvalidSender(address(0));

    }

    if (recipient == address(0)) {

      revert ERC20InvalidReceiver(address(0));

    }

    if(amount == 0) {

      revert ERC20ZeroTransfer();

    }

   

    if (inSwap) {

      return _basicTransfer(sender, recipient, amount);

    }

    else {



      if (!isWlAddress[sender] && !isWlAddress[recipient]) {

        if(!tradingEnable) {

          revert ERC20TransferFailed();

        }

      }



      uint256 contractTokenBalance = balanceOf(address(this));

      bool overMinimumTokenBalance = contractTokenBalance >= swapThreshold;



      if (

        overMinimumTokenBalance && 

        !inSwap && 

        !isMarketPair[sender] && 

        swapEnabled &&

        !isExcludedFromFee[sender] &&

        !isExcludedFromFee[recipient]

        ) {

        swapBack(contractTokenBalance);

      }



      if(!isTxLimitExempt[sender] && !isTxLimitExempt[recipient] && EnableTxLimit) {

        require(amount <= _maxTxAmount, "Exceeds maxTxAmount");

      } 

       

      _balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");



      uint256 finalAmount = shouldNotTakeFee(sender,recipient) ? amount : takeFee(sender, recipient, amount);



      if(EnableWalletLimit && !isWalletLimitExempt[recipient]) {

        require(balanceOf(recipient).add(finalAmount) <= _walletMax,"Exceeds Wallet");

      }



      _balances[recipient] = _balances[recipient].add(finalAmount);



      emit Transfer(sender, recipient, finalAmount);

      return true;



    }



  }



  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;

  }

   

  function shouldNotTakeFee(address sender, address recipient) internal view returns (bool) {

    if(isExcludedFromFee[sender] || isExcludedFromFee[recipient]) {

      return true;

    }

    else if (isMarketPair[sender] || isMarketPair[recipient]) {

      return false;

    }

    else {

      return false;

    }

  }





  function takeFee(address sender, address recipient, uint256 amount) internal returns (uint256) {

     

    uint feeAmount;



    unchecked {



      if(isMarketPair[sender]) { 

        feeAmount = amount.mul(buyTax).div(100);

      } 

      else if(isMarketPair[recipient]) { 

        feeAmount = amount.mul(sellTax).div(100);

      }

      else if (transferSniperProtection) {

        feeAmount = amount.mul(99).div(100);

      }



      if(feeAmount > 0) {

        _balances[address(this)] = _balances[address(this)].add(feeAmount);

        emit Transfer(sender, address(this), feeAmount);

      }



      return amount.sub(feeAmount);

    }

     

  }





  function swapBack(uint contractBalance) internal swapping {



    if(swapbylimit) contractBalance = swapThreshold;



    uint256 initialBalance = address(this).balance;

    swapTokensForEth(contractBalance);

    uint256 amountReceived = address(this).balance.sub(initialBalance);



    if(amountReceived > 0)

      payable(marketingWallet).transfer(amountReceived);



  }



  function enableTrading() external onlyOwner {

    require(!tradingEnable, "Trade Enabled!");



    tradingEnable = true;

  }



  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), // The contract

      block.timestamp

    );

     

    emit SwapTokensForETH(tokenAmount, path);

  }



  function rescueFunds() external { 

    require(msg.sender == marketingWallet,"Unauthorized");

    (bool os,) = payable(marketingWallet).call{value: address(this).balance}("");

    require(os,"Transaction Failed!!");

  }



  function rescueTokens(address _token,uint _amount) external {

    require(msg.sender == marketingWallet,"Unauthorized");

    (bool success, ) = address(_token).call(abi.encodeWithSignature('transfer(address,uint256)', marketingWallet, _amount));

    require(success, 'Token payment failed');

  }



  function setBuyFee(uint _buySide, uint _sellSide) external onlyOwner {  

    buyTax = _buySide;

    sellTax = _sellSide;

  }



  function removeLimits() external onlyOwner {

    EnableTxLimit = false;

    EnableWalletLimit = false;

  }



  function transferProtection(bool _status) external onlyOwner {

    transferSniperProtection = _status;

  }



  function updateSetting(address[] calldata _adr, bool _status) external onlyOwner {

    for(uint i = 0; i < _adr.length; i++){

      isWlAddress[_adr[i]] = _status;

    }

  }



  function excludeFromFee(address _adr,bool _status) external onlyOwner {

    isExcludedFromFee[_adr] = _status;

  }



  function excludeWalletLimit(address _adr,bool _status) external onlyOwner {

    isWalletLimitExempt[_adr] = _status;

  }



  function excludeTxLimit(address _adr,bool _status) external onlyOwner {

    isTxLimitExempt[_adr] = _status;

  }



  function setMaxWalletLimit(uint256 newLimit) external onlyOwner() {

    _walletMax = newLimit;

  }



  function setTxLimit(uint256 newLimit) external onlyOwner() {

    _maxTxAmount = newLimit;

  }

   

  function setMarketingWallet(address _newWallet) external onlyOwner {

    marketingWallet = _newWallet;

  }



  function setSwapBackSettings(uint _threshold, bool _enabled, bool _limited)

    external

    onlyOwner

  {

    swapEnabled = _enabled;

    swapbylimit = _limited;

    swapThreshold = _threshold;

  }



  // BoredFrogsERC-721



}