// File: src/FreeERC20.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import "solmate/tokens/ERC20.sol";
import "solmate/auth/Owned.sol";
import "./IUniswapV2Router01.sol";
// import "./IUniswapV2Factory.sol";
import "./SafeMath.sol";

contract ERC20Token is ERC20, Owned {
    using SafeMath for uint256;

    uint public max_holding;
    uint public max_transfer;
    uint public sell_tax_threshold;
    uint public buy_tax;
    uint public sell_tax;
    uint public in_swap = 1; // 1 is false, 2 is true
    uint public is_trading_enabled = 1; // 1 is false, 2 is true
	uint public maxSupply;

    address public tax_receiver;

    mapping(address => bool) public lps;
    mapping(address => bool) public routers;
    mapping(address => bool) public anti_whale_exceptions;
    mapping(address => bool) public tax_exceptions;

    address public weth;
    address public uni_router;
    address public uni_factory;

    constructor(
        string memory name,
        string memory ticker,
        uint8 decimals,
        uint _totalSupply,
		uint _maxSupply,

        uint _max_holding,
        uint _max_transfer,
        uint _buy_tax,
        uint _sell_tax,

        address _uni_router,
        address _weth
    ) ERC20(name, ticker, decimals) Owned(msg.sender) {
        require(_buy_tax <= 30, "buy tax too high");
        require(_sell_tax <= 30, "sell tax too high");
		require(_maxSupply >= _totalSupply, "Invalid max supply provided");

        require(
            _max_holding >= _totalSupply.div(100),
            "Max Holding Limit cannot be less than 1% of total supply"
        );
        require(
            _max_transfer >= _totalSupply.div(100),
            "Max Transfer Limit cannot be less than 1% of total supply"
        );

        max_holding = _max_holding;
        max_transfer = _max_transfer;

        sell_tax_threshold = _totalSupply / 500; // 0.5%
        buy_tax = _buy_tax;
        sell_tax = _sell_tax;
		maxSupply = _maxSupply;

        anti_whale_exceptions[address(this)] = true;
        anti_whale_exceptions[msg.sender] = true;

        uni_router = _uni_router;
        uni_factory = IUniswapV2Router01(uni_router).factory();
        weth = _weth;

        routers[_uni_router] = true;
        allowance[address(this)][_uni_router] = type(uint256).max;
        anti_whale_exceptions[_uni_router] = true;
        allowance[msg.sender][address(this)] = type(uint256).max;

        tax_receiver = owner;

        _mint(msg.sender, _totalSupply);
    }

    modifier lockTheSwap() {
        in_swap = 2;
        _;
        in_swap = 1;
    }

    function transfer(
        address to,
        uint256 amount
    ) public override returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) {
            require(allowed >= amount, "FreeERC20: Not enough allowance");

            allowance[from][msg.sender] = allowed - amount;
        }

        _transfer(from, to, amount);
        return true;
    }

    function _transfer(address from, address to, uint amount) private {
        require(
            is_trading_enabled == 2 || tx.origin == owner,
            "FreeERC20: Trading isnt live"
        );
        require(from != address(0), "FreeERC20: Transfer from the zero address");
        require(to != address(0), "FreeERC20: Transfer to the zero address");
        require(amount > 0, "FreeERC20: Transfer amount must be greater than zero");
        uint256 taxAmount = 0;
        if (from != owner && to != owner && tx.origin != owner) {
            bool isSelling;
            if (lps[from] && !routers[to] && !tax_exceptions[to]) {
                require(
                    max_transfer >= amount || anti_whale_exceptions[to],
                    "max tx limit"
                );

                taxAmount = amount.mul(buy_tax).div(100);
            }

            if (lps[to] && from != address(this) && !tax_exceptions[from]) {
                isSelling = true;
                require(
                    max_transfer >= amount || anti_whale_exceptions[from],
                    "FreeERC20: Max tx limit violation"
                );

                taxAmount = amount.mul(sell_tax).div(100);
            }

            uint256 contractTokenBalance = balanceOf[address(this)];
            if (
                in_swap == 1 &&
                isSelling &&
                contractTokenBalance > sell_tax_threshold
            ) {
                swapTokensForEth(contractTokenBalance);
            }
        }

        if (taxAmount > 0) {
            balanceOf[address(this)] = balanceOf[address(this)].add(taxAmount);
            emit Transfer(from, address(this), taxAmount);
        } else {
            require(
                max_transfer >= amount || anti_whale_exceptions[from],
                "FreeERC20: Max tx limit violation"
            );
        }

        balanceOf[from] = balanceOf[from].sub(amount);
        balanceOf[to] = balanceOf[to].add(amount.sub(taxAmount));

        require(
            balanceOf[to] <= max_holding ||
                anti_whale_exceptions[to] ||
                tx.origin == owner,
            "FreeERC20: Max holding limit violation"
        );

        emit Transfer(from, to, amount.sub(taxAmount));
    }

    function swapTokensForEth(uint amount) private lockTheSwap {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = weth;

        try IUniswapV2Router01(uni_router).swapExactTokensForETHSupportingFeeOnTransferTokens(
            amount,
            0,
            path,
            tax_receiver,
            type(uint).max
        ) {
            //
        } catch {
            // Ignore, to prevent calls from failing if owner sets invalid router
        }
    }

    function isAntiWhaleEnabled() external view returns (bool) {
        return max_holding != 0 || max_transfer != 0;
    }

	function mint(address receiver, uint amount) external onlyOwner {
		require(totalSupply + amount <= maxSupply, "Mint amount too high");

		_mint(receiver, amount);
	}

    function setLimits(
        uint _max_holding,
        uint _max_transfer
    ) external onlyOwner {
        if (_max_holding == 0) {
            _max_holding = type(uint256).max;
        }

        if (_max_transfer == 0) {
            _max_transfer = type(uint256).max;
        }

        require(
            _max_holding >= totalSupply.div(100),
            "FreeERC20: Max Holding Limit cannot be less than 1% of total supply"
        );
        require(
            _max_transfer >= totalSupply.div(100),
            "FreeERC20: Max Transfer Limit cannot be less than 1% of total supply"
        );

        max_holding = _max_holding;
        max_transfer = _max_transfer;
    }

    function setAntiWhaleException(address user, bool val) external onlyOwner {
        anti_whale_exceptions[user] = val;
    }

    function enableTrading() external onlyOwner {
        is_trading_enabled = 2;
    }

    function setUniRouter(address newRouter, address newFactory) external onlyOwner {
        uni_factory = newFactory;
        uni_router = newRouter;
        routers[newRouter] = true;
        allowance[address(this)][newRouter] = type(uint256).max;
        anti_whale_exceptions[newRouter] = true;
    }

    function setAmm(address lp) public onlyOwner {
        lps[lp] = true;
        anti_whale_exceptions[lp] = true;
    }

    function addRouter(address router) external onlyOwner {
        routers[router] = true;
        allowance[address(this)][router] = type(uint256).max;
        anti_whale_exceptions[router] = true;
    }

    function setExcludeFromFee(address addy, bool val) external onlyOwner {
        tax_exceptions[addy] = val;
    }

    function setSwapThreshold(uint newThreshold) external onlyOwner {
        sell_tax_threshold = newThreshold;
    }

    function setTaxes(uint _buy_tax, uint _sell_tax) external onlyOwner {
        require(_buy_tax <= 30, "FreeERC20: Buy tax too high");
        require(_sell_tax <= 30, "FreeERC20: Sell tax too high");

        buy_tax = _buy_tax;
        sell_tax = _sell_tax;
    }

    function setTaxReceiver(address _tax_receiver) external onlyOwner {
        tax_receiver = _tax_receiver;
    }

    function addLp(uint amount) external payable onlyOwner {
        is_trading_enabled = 2;
        _transfer(owner, address(this), amount);
        uint balance = address(this).balance;
        address pair = uniV2Pair();
        IUniswapV2Router01(uni_router).addLiquidityETH{value: balance}(
            address(this),
            amount,
            amount,
            balance,
            owner,
            block.timestamp
        );
        setAmm(pair);
        is_trading_enabled = 1;
    }

    function uniV2Pair() public view returns (address pair) {
        pair = pairFor(weth, address(this));
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(
        address tokenA,
        address tokenB
    ) internal view returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(
            uint160(
                uint(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            uni_factory,
                            keccak256(abi.encodePacked(token0, token1)),
                            hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash
                        )
                    )
                )
            )
        );
    }

    function sortTokens(
        address tokenA,
        address tokenB
    ) internal pure returns (address token0, address token1) {
        (token0, token1) = tokenA < tokenB
            ? (tokenA, tokenB)
            : (tokenB, tokenA);
    }

    receive() external payable {}

    function withdraw() external onlyOwner {
        (bool sent, ) = owner.call{value: address(this).balance}("");
        require(sent, "FreeERC20: Failed to send Ether");
    }

    function version() public pure returns (uint) {
        return 2;
    }
}

// File: lib/solmate/src/tokens/ERC20.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// File: lib/solmate/src/auth/Owned.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OwnershipTransferred(address indexed user, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}

// File: src/IUniswapV2Router01.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

// https://uniswap.org/docs/v2/smart-contracts/router01/
// https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router01.sol implementation
// UniswapV2Router01 is deployed at 0xf164fC0Ec4E93095b804a4795bBe1e041497b92a on the Ethereum mainnet, and the Ropsten, Rinkeby, Görli, and Kovan testnets

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 swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

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

// File: src/SafeMath.sol



pragma solidity ^0.8.19;


/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
    if (a == 0) {
      return 0;
    }
    c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    // uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return a / b;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
    c = a + b;
    assert(c >= a);
    return c;
  }
}