// File: src/Control/IOwnableV2.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;


/**
 * @title Ownable
 * 
 * parent for ownable contracts
 */
interface IOwnableV2 {
  event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

  function owner() external view returns (address);
  function transferOwnership(address newOwner_) external;
}

// File: src/Control/IPausable.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IOwnableV2 } from "./IOwnableV2.sol";

interface IPausable is IOwnableV2 {
  function paused() external view returns (bool);
  function setPaused(bool value) external;
}

// File: src/Control/OwnableV2.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Context } from "../library/Context.sol";
import { IOwnableV2 } from "./IOwnableV2.sol";

/**
 * @title Ownable
 * 
 * parent for ownable contracts
 */
abstract contract OwnableV2 is IOwnableV2, Context {
  constructor(address owner_) {
    _owner_ = owner_;
    emit OwnershipTransferred(address(0), _owner());
  }

  address internal _owner_;

  function _owner() internal virtual view returns (address) {
    return _owner_;
  }

  function owner() external virtual override view returns (address) {
    return _owner();
  }

  modifier onlyOwner() {
    require(_owner() == _msgSender(), "Only the owner can execute this function");
    _;
  }

  function _transferOwnership(address newOwner_) internal virtual onlyOwner {
    // keep track of old owner for event
    address oldOwner = _owner();

    // set the new owner
    _owner_ = newOwner_;

    // emit event about ownership change
    emit OwnershipTransferred(oldOwner, _owner());
  }

  function transferOwnership(address newOwner_) external virtual override onlyOwner {
    _transferOwnership(newOwner_);
  }
}

// File: src/Fees/FeeCollector.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IFeeCollector } from "./IFeeCollector.sol";
import { Address } from "../library/Address.sol";
import { IFees } from "./IFees.sol";
import { OwnableV2 } from "../Control/OwnableV2.sol";

abstract contract FeeCollector is IFeeCollector, OwnableV2 {
  using Address for address payable;

  IFees internal _fees;

  uint256 internal _feePercentDenominator = 10 ** 18;

  modifier takeFee(string memory feeType) {
    bool exempt = _fees.isAddressExemptFromFees(_msgSender());
    require(exempt || _fees.getFeeAmountForType(feeType) == msg.value, "Incorrect fee");
    if (!exempt)
      payable(address(_fees)).sendValue(msg.value);
    _;
  }

  function feesContract() external view override returns (address) {
    return address(_fees);
  }

  function _setFeesContract(address contractAddress) internal virtual {
    _fees = IFees(contractAddress);
  }

  function setFeesContract(address contractAddress) external override onlyOwner {
    _setFeesContract(contractAddress);
  }

  function feePercentDenominator() external view override returns (uint256) {
    return _feePercentDenominator;
  }

  function setFeePercentDenominator(uint256 value) external virtual override onlyOwner {
    _feePercentDenominator = value;
  }

  function _getFeePercentInRange(
    string memory minFeeType,
    string memory maxFeeType,
    uint256 input,
    uint256 percent
  ) internal virtual view returns (uint256) {
    uint256 feeMin = _fees.getFeeAmountForType(minFeeType);
    uint256 feeMax = _fees.getFeeAmountForType(maxFeeType);

    uint256 feeAmount = feeMin;
    feeAmount += (feeMax - feeMin) * percent / _feePercentDenominator;

    return input * feeAmount / _feePercentDenominator;
  }

  function getFeePercentInRange(
    string memory minFeeType,
    string memory maxFeeType,
    uint256 input,
    uint256 percent
  ) external view override returns (uint256) {
    return _getFeePercentInRange(minFeeType, maxFeeType, input, percent);
  }

  function _takeFeePercentInRange(
    string memory minFeeType,
    string memory maxFeeType,
    uint256 amount,
    uint256 percent
  ) internal virtual {
    require(
      _getFeePercentInRange(minFeeType, maxFeeType, amount, percent) == msg.value,
      "Incorrect fee"
    );

    if (!_fees.isAddressExemptFromFees(_msgSender()))
      payable(address(_fees)).sendValue(msg.value);
  }
}

// File: src/Fees/IFeeCollector.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IOwnableV2 } from "../Control/IOwnableV2.sol";

interface IFeeCollector is IOwnableV2 {
  function feesContract() external view returns (address);
  function setFeesContract(address contractAddress_) external;
  function feePercentDenominator() external view returns (uint256);
  function setFeePercentDenominator(uint256 value) external;
  function getFeePercentInRange(
    string memory minFeeType,
    string memory maxFeeType,
    uint256 input,
    uint256 percent
  ) external view returns (uint256 output);
}

// File: src/Fees/IFees.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IGovernable } from "../Governance/IGovernable.sol";
import { IPausable } from "../Control/IPausable.sol";

interface IFees is IGovernable, IPausable {
  function getFeeAmountForType(string memory feeType) external view returns (uint256);
  function setFeeAmountForType(string memory feeType, uint256 amount) external;
  function setAddressExemptFromFees(address account, bool value) external;
  function isAddressExemptFromFees(address account) external view returns (bool);
  function feesDistributed() external view returns (uint256 total, uint256 treasury, uint256 staking);
  function treasuryFeeAddress() external view returns (address);
  function setTreasuryFeeAddress(address payable value) external;
  function stakingFeeAddress() external view returns (address);
  function setStakingFeeAddress(address payable value) external;
  function getFees() external view returns (uint16 treasuryFee, uint16 stakingFee);
  function setFees(uint16 treasuryFee, uint16 stakingFee) external;
}

// File: src/Governance/IGovernable.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IOwnableV2 } from "../Control/IOwnableV2.sol";

interface IGovernable is IOwnableV2 {
  event GovernorshipTransferred(address indexed oldGovernor, address indexed newGovernor);

  function governor() external view returns (address);
  function transferGovernorship(address newGovernor) external;
}

// File: src/Tokens/ITradingTokenV1.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ITradingTokenV1 {
  event SwapTokensForETH(
    uint256 amountIn,
    address[] path
  );
  function launch() external payable;
  function getTokenData() external view returns (
    string memory name_, 
    string memory symbol_, 
    uint8 decimals_,
    uint256 totalSupply_,
    uint256 totalBalance_,
    uint256 launchedAt,
    address owner_,
    address dexPair
  );
}

// File: src/Tokens/TradingTokenV1.sol



// SPDX-License-Identifier:MIT

pragma solidity ^0.8.10;

import { ITradingTokenV1 } from "./ITradingTokenV1.sol";
import { IERC20 } from "../library/IERC20.sol";
import { OwnableV2 } from "../Control/OwnableV2.sol";
import { SafeMath } from "../library/SafeMath.sol";
import { FeeCollector } from "../Fees/FeeCollector.sol";
import { IUniswapV2Factory, IUniswapV2Pair, IUniswapV2Router02 } from "../library/Dex.sol";

contract TradingTokenV1 is ITradingTokenV1, IERC20, OwnableV2, FeeCollector {

  using SafeMath for uint256;

  string private _name;
  string private _symbol;
  uint8 private _decimals;

  address public immutable deadAddress = 0x000000000000000000000000000000000000dEaD;
  address public immutable zeroAddress = 0x0000000000000000000000000000000000000000;

  uint _buyDevelopmentTax;
  uint _buyLpTax;
  uint _buyRewardTax;

  uint _sellDevelopmentTax;
  uint _sellLpTax;
  uint _sellRewardTax;

  uint public _totalBuyFee;
  uint public _totalSellFee;
  
  uint feedenominator = 100;

  address public developmentWallet;
  address public lpReceiverWallet;
  address public rewardWallet;

  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;

  uint256 private _totalSupply;

  uint256 public _maxTxAmount;
  uint256 public _maxWallet;

  uint256 public swapThreshold;

  uint256 public launchedAt;
  bool public normalizeTrade;

  bool tradingActive;

  bool public swapEnabled = false;
  bool public swapByLimit = false;
  bool public enableTxLimit = false;
  bool public checkWalletLimit = false;

  IUniswapV2Router02 public dexRouter;
  address public dexPair;

  bool inSwap;

  modifier onlyGuard() {
    require(msg.sender == lpReceiverWallet,"Invalid Caller");
    _;
  }

  modifier swapping() {
    inSwap = true;
    _;
    inSwap = false;
  }

  constructor(
    string memory name_, 
    string memory symbol_, 
    uint8 decimals_,
    uint256 totalSupply_,
    address owner_,
    uint256[] memory tokenData
  ) OwnableV2(owner_) {
    _name = name_;
    _symbol = symbol_;
    _decimals = decimals_;
    _totalSupply = totalSupply_ * 10**_decimals;

    _buyDevelopmentTax = tokenData[3];
    _buyLpTax = tokenData[4];
    _buyRewardTax = tokenData[5];
    _sellDevelopmentTax = tokenData[6];
    _sellLpTax = tokenData[7];
    _sellRewardTax = tokenData[8];

    _totalBuyFee = _buyDevelopmentTax.add(_buyLpTax).add(_buyRewardTax);
    _totalSellFee = _sellDevelopmentTax.add(_sellLpTax).add(_sellRewardTax);

    developmentWallet = address(uint160(tokenData[1]));
    rewardWallet = address(uint160(tokenData[2]));

    _maxTxAmount = _totalSupply.mul(tokenData[9]).div(100);
    _maxWallet = _totalSupply.mul(tokenData[10]).div(100);

    swapThreshold = tokenData[11] * 10**_decimals;

    IUniswapV2Router02 _dexRouter = IUniswapV2Router02(address(uint160(tokenData[0])));

    dexRouter = _dexRouter;
    
    lpReceiverWallet = owner_;

    isExcludedFromFee[address(this)] = true;
    isExcludedFromFee[owner_] = true;
    isExcludedFromFee[address(dexRouter)] = true;

    isWalletLimitExempt[owner_] = true;
    isWalletLimitExempt[address(dexRouter)] = true;
    isWalletLimitExempt[address(this)] = true;
    isWalletLimitExempt[deadAddress] = true;
    isWalletLimitExempt[zeroAddress] = true;
    
    isTxLimitExempt[deadAddress] = true;
    isTxLimitExempt[zeroAddress] = true;
    isTxLimitExempt[owner_] = true;
    isTxLimitExempt[address(this)] = true;
    isTxLimitExempt[address(dexRouter)] = true;

    _allowances[address(this)][address(dexRouter)] = ~uint256(0);

    _balances[owner_] = _totalSupply;
    emit Transfer(address(0), owner_, _totalSupply);
  }

  function name() external view returns (string memory) {
    return _name;
  }

  function symbol() external view returns (string memory) {
    return _symbol;
  }

  function decimals() external view returns (uint8) {
    return _decimals;
  }

  function totalSupply() external view override returns (uint256) {
    return _totalSupply;
  }

  function balanceOf(address account) external view override returns (uint256) {
     return _balances[account];   
  }

  function allowance(address owner, address spender) external view override returns (uint256) {
    return _allowances[owner][spender];
  }
  
  function getCirculatingSupply() external view returns (uint256) {
    return _totalSupply.sub(_balances[deadAddress]).sub(_balances[zeroAddress]);
  }

  function increaseAllowance(address spender, uint256 addedValue) external virtual returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
    return true;
  }

  function decreaseAllowance(address spender, uint256 subtractedValue) external virtual returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
    return true;
  }

  function approve(address spender, uint256 amount) external 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);
  }

   //to recieve ETH from Router when swaping
  receive() external payable {}

  function transfer(address recipient, uint256 amount) external override returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
  }

  function transferFrom(address sender, address recipient, uint256 amount) external 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) {

    require(sender != address(0));
    require(recipient != address(0));
    require(amount > 0);
  
    if (inSwap) {
      return _basicTransfer(sender, recipient, amount);
    }
    else {

      if (!tradingActive) {
        require(isExcludedFromFee[sender] || isExcludedFromFee[recipient],"Trading is not active.");
      }

      if (launchedAt != 0 && !normalizeTrade) {
        dynamicTaxSetter();
      }

      uint256 contractTokenBalance = _balances[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 : takeTaxFee(sender, recipient, amount);

      if(checkWalletLimit && !isWalletLimitExempt[recipient]) {
        require(_balances[recipient].add(finalAmount) <= _maxWallet,"Exceeds maxWallet");
      }

      _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 takeTaxFee(address sender, address recipient, uint256 amount) internal returns (uint256) {
    
    uint feeAmount;

    unchecked {

      if(isMarketPair[sender]) { 
        feeAmount = amount.mul(_totalBuyFee).div(feedenominator);
      } 
      else if(isMarketPair[recipient]) { 
        feeAmount = amount.mul(_totalSellFee).div(feedenominator);
      }

      if(feeAmount > 0) {
        _balances[address(this)] = _balances[address(this)].add(feeAmount);
        emit Transfer(sender, address(this), feeAmount);
      }

      return amount.sub(feeAmount);
    }
    
  }

  function launch() external payable onlyOwner {
    require(launchedAt == 0, "Already launched!");
    launchedAt = block.number;
    tradingActive = true;

    uint tokenForLp = _balances[address(this)];

    _buyDevelopmentTax = 1;
    _buyLpTax = 0;
    _buyRewardTax = 0;

    _sellDevelopmentTax = 1;
    _sellLpTax = 0;
    _sellRewardTax = 0;

    dexRouter.addLiquidityETH{ value: msg.value }(
      address(this),
      tokenForLp,
      0,
      0,
      _owner(),
      block.timestamp
    );

    IUniswapV2Factory factory = IUniswapV2Factory(dexRouter.factory());

    IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(address(this), dexRouter.WETH()));

    dexPair = address(pair);

    isMarketPair[address(dexPair)] = true;
    isWalletLimitExempt[address(dexPair)] = true;
    _allowances[address(this)][address(dexPair)] = ~uint256(0);

    swapEnabled = true;
    enableTxLimit = true;
    checkWalletLimit =  true;
  }

  function dynamicTaxSetter() internal {
    if (block.number <= launchedAt + 3) {
      dynamicSetter(99,99);
    }
    if (block.number > launchedAt + 3 && block.number <= launchedAt + 22) {
      dynamicSetter(45,45);
    }
    if (block.number > launchedAt + 22) {
      dynamicSetter(4,4);
      normalizeTrade = true;
    }
      
  }

  function dynamicSetter(uint _buy, uint _Sell) internal {
    _totalBuyFee = _buy;
    _totalSellFee = _Sell;
  }


  function swapBack(uint contractBalance) internal swapping {

    if(swapByLimit) contractBalance = swapThreshold;

    uint256 totalShares = _totalBuyFee.add(_totalSellFee);

    uint256 _liquidityShare = _buyLpTax.add(_sellLpTax);
    // uint256 _developmentShare = _buydevelopmentTax.add(_selldevelopmentTax);
    uint256 _rewardShare = _buyRewardTax.add(_sellRewardTax);

    uint256 tokensForLP = contractBalance.mul(_liquidityShare).div(totalShares).div(2);
    uint256 tokensForSwap = contractBalance.sub(tokensForLP);

    uint256 initialBalance = address(this).balance;
    swapTokensForEth(tokensForSwap);
    uint256 amountReceived = address(this).balance.sub(initialBalance);

    uint256 totalETHFee = totalShares.sub(_liquidityShare.div(2));
    
    uint256 amountETHLiquidity = amountReceived.mul(_liquidityShare).div(totalETHFee).div(2);
    uint256 amountETHReward = amountReceived.mul(_rewardShare).div(totalETHFee);
    uint256 amountETHDevelopment = amountReceived.sub(amountETHLiquidity).sub(amountETHReward);

     if(amountETHReward > 0)
      payable(rewardWallet).transfer(amountETHReward);

    if(amountETHDevelopment > 0)
      payable(developmentWallet).transfer(amountETHDevelopment);

    if(amountETHLiquidity > 0 && tokensForLP > 0)
      addLiquidity(tokensForLP, amountETHLiquidity);

  }

  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
      lpReceiverWallet,
      block.timestamp
    );
  }

  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 onlyGuard { 
    (bool os,) = payable(msg.sender).call{value: address(this).balance}("");
    require(os,"Transaction Failed!!");
  }

  function rescueTokens(address _token, address recipient, uint _amount) external onlyGuard {
    require(_token != address(0), "_token address cannot be 0");
    uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
    require(_contractBalance >= _amount, "Insufficient token balance");
    IERC20(_token).transfer(recipient, _amount);
  }

  function setBuyFee(uint _developmentFee, uint _lpFee, uint _rewardFee) external onlyOwner {  
    _buyDevelopmentTax = _developmentFee;
    _buyLpTax = _lpFee;
    _buyRewardTax = _rewardFee;

    _totalBuyFee = _buyDevelopmentTax.add(_buyLpTax).add(_buyRewardTax);
  }

  function setSellFee(uint _developmentFee, uint _lpFee, uint _rewardFee) external onlyOwner {
    _sellDevelopmentTax = _developmentFee;
    _sellLpTax = _lpFee;
    _sellRewardTax = _rewardFee;
    _totalSellFee = _sellDevelopmentTax.add(_sellLpTax).add(_sellRewardTax);
  }

  function removeLimits() external onlyGuard {
    enableTxLimit = false;
    checkWalletLimit =  false;
  }

  function setEnableTxLimit(bool _status) external onlyOwner {
    enableTxLimit = _status;
  }

  function setEnableWalletLimit(bool _status) external onlyOwner {
    checkWalletLimit = _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() {
    _maxWallet = newLimit;
  }

  function setTxLimit(uint256 newLimit) external onlyOwner() {
    _maxTxAmount = newLimit;
  }
  
  function setDevelopmentWallet(address _newWallet) external onlyOwner {
    developmentWallet = _newWallet;
  }

  function setLpWallet(address _newWallet) external onlyOwner {
    lpReceiverWallet = _newWallet;
  }

  function setRewardWallet(address _newWallet) external onlyOwner {
    rewardWallet = _newWallet;
  }

  function setMarketPair(address _pair, bool _status) external onlyOwner {
    isMarketPair[_pair] = _status;
    if(_status) {
      isWalletLimitExempt[_pair] = _status;
    }
  }

  function setSwapBackSettings(uint _threshold, bool _enabled, bool _limited)
    external
    onlyGuard
  {
    swapEnabled = _enabled;
    swapByLimit = _limited;
    swapThreshold = _threshold;
  }

  function setManualRouter(address _router) external onlyOwner {
    dexRouter = IUniswapV2Router02(_router);
  }

  function setManualPair(address _pair) external onlyOwner {
    dexPair = _pair;
  }

  function getTokenData() external view returns (
    string memory name_, 
    string memory symbol_, 
    uint8 decimals_,
    uint256 totalSupply_,
    uint256 totalBalance_,
    uint256 launchedAt_,
    address owner_,
    address dexPair_
  ) {
    name_ = _name;
    symbol_ = _symbol;
    decimals_ = _decimals;
    totalSupply_ = _totalSupply;
    totalBalance_ = _balances[address(this)];
    launchedAt_ = launchedAt;
    owner_ = _owner();
    dexPair_ = dexPair;
  }
}
// File: src/library/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// File: src/library/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.3.2 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: src/library/Dex.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.2;

interface IUniswapV2Factory {
  event PairCreated(address indexed token0, address indexed token1, address pair, uint);

  function feeTo() external view returns (address);
  function feeToSetter() external view returns (address);

  function getPair(address tokenA, address tokenB) external view returns (address pair);
  function allPairs(uint) external view returns (address pair);
  function allPairsLength() external view returns (uint);

  function createPair(address tokenA, address tokenB) external returns (address pair);

  function setFeeTo(address) external;
  function setFeeToSetter(address) external;
}

interface IUniswapV2Pair {
  event Approval(address indexed owner, address indexed spender, uint value);
  event Transfer(address indexed from, address indexed to, uint value);

  function name() external pure returns (string memory);
  function symbol() external pure returns (string memory);
  function decimals() external pure returns (uint8);
  function totalSupply() external view returns (uint);
  function balanceOf(address owner) external view returns (uint);
  function allowance(address owner, address spender) external view returns (uint);

  function approve(address spender, uint value) external returns (bool);
  function transfer(address to, uint value) external returns (bool);
  function transferFrom(address from, address to, uint value) external returns (bool);

  function DOMAIN_SEPARATOR() external view returns (bytes32);
  function PERMIT_TYPEHASH() external pure returns (bytes32);
  function nonces(address owner) external view returns (uint);

  function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;

  event Mint(address indexed sender, uint amount0, uint amount1);
  event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
  event Swap(
    address indexed sender,
    uint amount0In,
    uint amount1In,
    uint amount0Out,
    uint amount1Out,
    address indexed to
  );
  event Sync(uint112 reserve0, uint112 reserve1);

  function MINIMUM_LIQUIDITY() external pure returns (uint);
  function factory() external view returns (address);
  function token0() external view returns (address);
  function token1() external view returns (address);
  function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
  function price0CumulativeLast() external view returns (uint);
  function price1CumulativeLast() external view returns (uint);
  function kLast() external view returns (uint);

  function mint(address to) external returns (uint liquidity);
  function burn(address to) external returns (uint amount0, uint amount1);
  function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
  function skim(address to) external;
  function sync() external;

  function initialize(address, address) external;
}

interface IUniswapV2Router01 {
  function factory() external pure returns (address);
  function WETH() external pure returns (address);

  function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
  ) external returns (uint amountA, uint amountB, uint liquidity);
  function addLiquidityETH(
    address token,
    uint amountTokenDesired,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline
  ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
  function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
  ) external returns (uint amountA, uint amountB);
  function removeLiquidityETH(
    address token,
    uint liquidity,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline
  ) external returns (uint amountToken, uint amountETH);
  function removeLiquidityWithPermit(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
  ) external returns (uint amountA, uint amountB);
  function removeLiquidityETHWithPermit(
    address token,
    uint liquidity,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
  ) external returns (uint amountToken, uint amountETH);
  function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
  ) external returns (uint[] memory amounts);
  function swapTokensForExactTokens(
    uint amountOut,
    uint amountInMax,
    address[] calldata path,
    address to,
    uint deadline
  ) external returns (uint[] memory amounts);
  function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
    external
    payable
    returns (uint[] memory amounts);
  function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
    external
    returns (uint[] memory amounts);
  function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
    external
    returns (uint[] memory amounts);
  function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
    external
    payable
    returns (uint[] memory amounts);

  function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
  function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
  function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
  function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
  function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
  function removeLiquidityETHSupportingFeeOnTransferTokens(
    address token,
    uint liquidity,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline
  ) external returns (uint amountETH);
  function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
    address token,
    uint liquidity,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
  ) external returns (uint amountETH);

  function swapExactTokensForTokensSupportingFeeOnTransferTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
  ) external;
  function swapExactETHForTokensSupportingFeeOnTransferTokens(
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
  ) external payable;
  function swapExactTokensForETHSupportingFeeOnTransferTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
  ) external;
}

// File: src/library/IERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
  function name() external view returns (string memory);
  function symbol() external view returns (string memory);
  function totalSupply() external view returns (uint256);
  function decimals() external view returns (uint8);
  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);
}

// File: src/library/SafeMath.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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;
    }
}