// File: 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: ERC20UniswapV2InternalSwaps.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.18;

import {ERC20} from "ERC20.sol";

interface IUniswapV2Pair {
    function getReserves()
        external
        view
        returns (uint112 reserve0, uint112 reserve1);

    function swap(
        uint amount0Out,
        uint amount1Out,
        address to,
        bytes calldata data
    ) external;

    function mint(address to) external;
}

interface IUniswapV2Factory {
    function createPair(
        address tokenA,
        address tokenB
    ) external returns (address pair);
}

interface IERC20 {
    function transferFrom(address from, address to, uint amount) external;

    function balanceOf(address account) external view returns (uint);

    function approve(address spender, uint256 amount) external;
}

interface IWETH {
    function deposit() external payable;
}

/**
 * @notice UniswapV2Pair does not allow to receive to token0 or token1.
 * As a workaround, this contract can receive tokens and has max approval
 * for the creator.
 */
contract ERC20HolderWithApproval {
    constructor(address token) {
        IERC20(token).approve(msg.sender, type(uint256).max);
    }
}

/**
 * @notice Gas optimized ERC20 token based on solmate's ERC20 contract.
 * @dev Optimizations assume a UniswapV2 WETH pair as main liquidity.
 */
abstract contract ERC20UniswapV2InternalSwaps is ERC20 {
    address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address private constant FACTORY =
        0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    address private immutable wethReceiver;
    address public immutable pair;

    error InvalidAddress();

    constructor() {
        // assumption to save additional gas
        if (address(this) >= WETH) {
            revert InvalidAddress();
        }
        pair = IUniswapV2Factory(FACTORY).createPair(address(this), WETH);
        wethReceiver = address(new ERC20HolderWithApproval(WETH));
    }

    /**
     * @dev Swap tokens to WETH directly on pair, to save gas.
     * No check for minimal return, susceptible to price manipulation!
     */
    function _swapForWETH(uint amountToken, address to) internal {
        uint amountWeth = _getAmountWeth(amountToken);
        _transferFromContractBalance(pair, amountToken);
        // Pair prevents receiving tokens to one of the pairs addresses
        IUniswapV2Pair(pair).swap(0, amountWeth, wethReceiver, new bytes(0));
        IERC20(WETH).transferFrom(wethReceiver, to, amountWeth);
    }

    /**
     * @dev Add tokens and WETH to liquidity, directly on pair, to save gas.
     * No check for minimal return, susceptible to price manipulation!
     * Sufficient WETH in contract balancee assumed!
     */
    function _addLiquidity(
        uint amountToken,
        address to
    ) internal returns (uint amountWeth) {
        amountWeth = _quoteToken(amountToken);
        _transferFromContractBalance(pair, amountToken);
        IERC20(WETH).transferFrom(address(this), pair, amountWeth);
        IUniswapV2Pair(pair).mint(to);
    }

    /**
     * @dev Add tokens and WETH as initial liquidity, directly on pair, to save gas.
     * No checks performed. Caller has to make sure to have access to the token before public!
     * Sufficient WETH in contract balancee assumed!
     */
    function _addInitialLiquidity(
        uint amountToken,
        uint amountWeth,
        address to
    ) internal {
        _transferFromContractBalance(pair, amountToken);
        IERC20(WETH).transferFrom(address(this), pair, amountWeth);
        IUniswapV2Pair(pair).mint(to);
    }

    /**
     * @dev Add tokens and ETH as initial liquidity, directly on pair, to save gas.
     * No checks performed. Caller has to make sure to have access to the token before public!
     * Sufficient ETH in contract balancee assumed!
     */
    function _addInitialLiquidityEth(
        uint amountToken,
        uint amountEth,
        address to
    ) internal {
        IWETH(WETH).deposit{value: amountEth}();
        _addInitialLiquidity(amountToken, amountEth, to);
    }

    /** @dev Transfer all WETH from contract balance to `to`. */
    function _sweepWeth(address to) internal returns (uint amountWeth) {
        amountWeth = IERC20(WETH).balanceOf(address(this));
        IERC20(WETH).transferFrom(address(this), to, amountWeth);
    }

    /** @dev Transfer all ETH from contract balance to `to`. */
    function _sweepEth(address to) internal {
        _safeTransferETH(to, address(this).balance);
    }

    /** @dev Quote `amountToken` in ETH, assuming no fees (used for liquidity). */
    function _quoteToken(
        uint amountToken
    ) internal view returns (uint amountEth) {
        (uint reserveToken, uint reserveEth) = IUniswapV2Pair(pair)
            .getReserves();
        amountEth = (amountToken * reserveEth) / reserveToken;
    }

    /** @dev Quote `amountToken` in WETH, assuming 0.3% uniswap fees (used for swap). */
    function _getAmountWeth(
        uint amounToken
    ) internal view returns (uint amountWeth) {
        (uint reserveToken, uint reserveWeth) = IUniswapV2Pair(pair)
            .getReserves();
        uint amountTokenWithFee = amounToken * 997;
        uint numerator = amountTokenWithFee * reserveWeth;
        uint denominator = (reserveToken * 1000) + amountTokenWithFee;
        amountWeth = numerator / denominator;
    }

    /** @dev Quote `amountWeth` in tokens, assuming 0.3% uniswap fees (used for swap). */
    function _getAmountToken(
        uint amounWeth,
        uint reserveToken,
        uint reserveWeth
    ) internal pure returns (uint amountToken) {
        uint numerator = reserveToken * amounWeth * 1000;
        uint denominator = (reserveWeth - amounWeth) * 997;
        amountToken = (numerator / denominator) + 1;
    }

    /** @dev Get reserves of pair. */
    function _getReserve()
        internal
        view
        returns (uint reserveToken, uint reserveWeth)
    {
        (reserveToken, reserveWeth) = IUniswapV2Pair(pair).getReserves();
    }

    /** @dev Transfer `amount` ETH to `to` gas efficiently. */
    function _safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly { // solhint-disable-line no-inline-assembly
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /** @dev Returns true if `_address` is a contract. */
    function _isContract(address _address) internal view returns (bool) {
        uint32 size;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            size := extcodesize(_address)
        }
        return (size > 0);
    }

    /** @dev Transfeer `amount` tokens from contract balance to `to`. */
    function _transferFromContractBalance(
        address to,
        uint256 amount
    ) internal returns (bool) {
        balanceOf[address(this)] -= amount;

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

        emit Transfer(address(this), to, amount);

        return true;
    }
}

// File: 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: SixPackRick.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.18;

import {ERC20UniswapV2InternalSwaps, ERC20} from "./ERC20UniswapV2InternalSwaps.sol";
import {Owned} from "Owned.sol";

/**
 * @title 6 Pack Rick token contract
 *
 * @notice 6PR has a 2% transfer tax, with 50% of tax used for adding liquidity.
 * 6PR is launched as a fair launch with 132 ETH hardcap.
 *
 * https://6packrick.com
 */
contract SixPackRick is
    ERC20("6PackRick", "6PR", 18),
    ERC20UniswapV2InternalSwaps,
    Owned
{
    /** @notice The presale states. */
    enum PresaleState {
        CLOSED,
        OPEN_FOR_WHITELIST,
        OPEN_FOR_PUBLIC,
        COMPLETED
    }

    /** @notice Percentage of supply allocated for presale participants (50%). */
    uint256 public constant SHARE_PRESALE = 50;
    /** @notice Percentage of supply allocated for initial liquidity (30%).*/
    uint256 public constant SHARE_LIQUIDITY = 30;
    /** @notice Percentage of supply allocated for team, marketing, cex listings, etc. (20%). */
    uint256 public constant SHARE_OTHER = 20;
    /** @notice Hardcap in ETH for presale (132 ETH). */
    uint256 public constant PRESALE_HARDCAP = 132 ether;
    /** @notice Per account limit in ETH for presale (0.6 ETH). */
    uint256 public constant PRESALE_ACCOUNT_LIMIT = 0.6 ether;
    /** @notice Minimum threshold in ETH to trigger #swapTokensAndAddLiquidity. */
    uint256 public constant SWAP_THRESHOLD_ETH_MIN = 0.005 ether;
    /** @notice Maximum threshold in ETH to trigger #swapTokensAndAddLiquidity. */
    uint256 public constant SWAP_THRESHOLD_ETH_MAX = 50 ether;
    /** @notice Transfer tax in percent (2%). 50% of this fee is used to add liquidity. */
    uint256 public constant TRANSFER_TAX = 2;

    uint256 private constant _MAX_SUPPLY = 420_690_000_000_000 ether;
    uint256 private constant _SUPPLY_PRESALE =
        (_MAX_SUPPLY * SHARE_PRESALE) / 100;
    uint256 private constant _SUPPLY_LIQUIDITY =
        (_MAX_SUPPLY * SHARE_LIQUIDITY) / 100;
    uint256 private constant _SUPPLY_OTHER =
        _MAX_SUPPLY - _SUPPLY_PRESALE - _SUPPLY_LIQUIDITY;

    /** @notice Tax recipient wallet. */
    address public taxRecipient = 0x9134A698F674D3a0D9154Ce812662526E279A8EF;
    /** @notice Whether address is extempt from transfer tax. */
    mapping(address => bool) public taxFreeAccount;
    /** @notice Whether address is an exchange pool. */
    mapping(address => bool) public isExchangePool;
    /** @notice Threshold in ETH of tokens to collect before triggering #swapTokensAndAddLiquidity. */
    uint256 public swapThresholdEth = 0.1 ether;
    /** @notice Tax manager. @dev Can **NOT** change transfer taxes. */
    address public taxManager;
    /** @notice Whether address is whitelisted for early presale access. */
    mapping(address => bool) public presaleWhitelist;
    /** @notice Presale commitment in ETH per address. */
    mapping(address => uint256) public commitment;
    /** @notice Presale amount of claimed tokens per address. */
    mapping(address => uint256) public claimedTokens;
    /** @notice Presale total commitment in ETH. */
    uint256 public totalCommitments;
    /** @notice Presale total amount of claimed tokens. */
    uint256 public totalClaimed;
    /** @notice Current presale state. */
    PresaleState public presaleState;
    /** @notice Whether buys and sells in the same block are blocked to prevent sandwitch attacks. */
    bool public limitPerBlockTransfers = true;

    /** @notice Anti sniper bot measure for initial opening of trading. */
    uint256 private _tradeableAfterBlock = type(uint256).max;
    /** @notice Anti sandwitch bot measure while limitPerBlockTransfers is enabled. */
    mapping(bytes32 => bool) private _perBlock;

    event CommitedToPresale(address indexed account, uint256 amount);
    event PresaleOpened();
    event TradingEnabled();
    event PublicPresaleOpened();
    event PresaleCompleted(uint256 totalCommitments);
    event PresaleClaimed(address indexed account, uint256 amount);
    event TaxRecipientChanged(address indexed taxRecipient);
    event SwapThresholdChanged(uint256 swapThresholdEth);
    event TaxFreeStateChanged(address indexed account, bool indexed taxFree);
    event ExchangePoolStateChanged(
        address indexed account,
        bool indexed isExchangePool
    );
    event TaxManagerChanged(address indexed taxManager);
    event LimitPerBlockTransfersChangeed(bool indexed limitPerBlockTransfers);
    event SwappedTokensAndAddedLiquidity(
        uint256 tokensSwapped,
        uint256 tokensAddedToLiquidity,
        uint256 wethAddedToLiquidity,
        uint256 wethCollected
    );

    error TradingNotOpenYet();
    error MaxAccountLimitExceeded();
    error HardcapExceeded();
    error NotWhitelistedForPresale();
    error PresaleClosed();
    error PresaleNotCompleted();
    error AlreadyClaimed();
    error NothingCommitted();
    error Unauthorized();
    error InvalidSwapThreshold();
    error NoContract();
    error TradeAlreadyOpend();
    error InvalidBlockDelay();
    error InvalidParameters();
    error InvalidState();
    error NoSameBlockBuySell();
    error ZeroTransfer();
    error NoCommittments();
    error TransferToZeroAddress();

    modifier onlyTaxManager() {
        if (msg.sender != taxManager) {
            revert Unauthorized();
        }
        _;
    }

    constructor() Owned(msg.sender) {
        taxManager = msg.sender;
        emit TaxManagerChanged(msg.sender);

        taxFreeAccount[msg.sender] = true;
        emit TaxFreeStateChanged(msg.sender, true);
        taxFreeAccount[taxRecipient] = true;
        emit TaxFreeStateChanged(taxRecipient, true);
        isExchangePool[pair] = true;
        emit ExchangePoolStateChanged(pair, true);

        _mint(address(this), _SUPPLY_PRESALE + _SUPPLY_LIQUIDITY);
        _mint(msg.sender, _SUPPLY_OTHER);
    }

    /** @dev Users can send ETH directly to **this** contract to participate */
    receive() external payable {
        commitToPresale();
    }

    /** @notice IERC20#transfer */
    function transfer(
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        if (amount == 0) {
            revert ZeroTransfer();
        }
        if (to == address(0)) {
            revert TransferToZeroAddress();
        }
        if (!taxFreeAccount[msg.sender] && !taxFreeAccount[to]) {
            if (block.number <= _tradeableAfterBlock) {
                revert TradingNotOpenYet();
            }
            if (limitPerBlockTransfers) {
                _enforceTransferLimit(msg.sender, to);
            }

            uint256 fee = (amount * TRANSFER_TAX) / 100;
            super.transfer(address(this), fee);
            unchecked {
                amount -= fee;
            }

            if (isExchangePool[to]) {
                _swapTokensAndAddLiquidity(swapThresholdToken());
            }
        }
        return super.transfer(to, amount);
    }

    /** @notice IERC20#transferFrom */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        if (amount == 0) {
            revert ZeroTransfer();
        }
        if (to == address(0)) {
            revert TransferToZeroAddress();
        }
        if (
            !taxFreeAccount[from] &&
            !taxFreeAccount[to] &&
            !taxFreeAccount[msg.sender]
        ) {
            if (block.number <= _tradeableAfterBlock) {
                revert TradingNotOpenYet();
            }
            if (limitPerBlockTransfers) {
                _enforceTransferLimit(from, to);
            }

            uint256 fee = (amount * TRANSFER_TAX) / 100;
            super.transferFrom(from, address(this), fee);
            unchecked {
                amount -= fee;
            }

            if (isExchangePool[to]) {
                _swapTokensAndAddLiquidity(swapThresholdToken());
            }
        }
        return super.transferFrom(from, to, amount);
    }

    // *** Tax Manager Interface ***

    /**
     * @notice Set `taxFree` state of `account`.
     * @param account account
     * @param taxFree true if `account` should be extempt from transfer taxes.
     * @dev Only callable by taxManager.
     */
    function setTaxFreeAccount(
        address account,
        bool taxFree
    ) external onlyTaxManager {
        if (taxFreeAccount[account] == taxFree) {
            revert InvalidParameters();
        }
        taxFreeAccount[account] = taxFree;
        emit TaxFreeStateChanged(account, taxFree);
    }

    /**
     * @notice Set `exchangePool` state of `account`
     * @param account account
     * @param exchangePool whether `account` is an exchangePool
     * @dev ExchangePool state is used to decide if transfer is a swap
     * and should trigger #swapTokensAndAddLiquidity.
     */
    function setExchangePool(
        address account,
        bool exchangePool
    ) external onlyTaxManager {
        if (isExchangePool[account] == exchangePool) {
            revert InvalidParameters();
        }
        isExchangePool[account] = exchangePool;
        emit ExchangePoolStateChanged(account, exchangePool);
    }

    /**
     * @notice Transfer taxManager role to `newTaxManager`.
     * @param newTaxManager new taxManager
     * @dev Only callable by taxManager.
     */
    function transferTaxManager(address newTaxManager) external onlyTaxManager {
        if (newTaxManager == taxManager) {
            revert InvalidParameters();
        }
        taxManager = newTaxManager;
        emit TaxManagerChanged(newTaxManager);
    }

    /**
     * @notice Change the amount of tokens collected via tax before a swap is triggered.
     * @param newSwapThresholdEth new threshold received in ETH
     * @dev Only callable by taxManager
     */
    function setSwapThresholdEth(
        uint256 newSwapThresholdEth
    ) external onlyTaxManager {
        if (
            newSwapThresholdEth < SWAP_THRESHOLD_ETH_MIN ||
            newSwapThresholdEth > SWAP_THRESHOLD_ETH_MAX ||
            newSwapThresholdEth == swapThresholdEth
        ) {
            revert InvalidSwapThreshold();
        }
        swapThresholdEth = newSwapThresholdEth;
        emit SwapThresholdChanged(newSwapThresholdEth);
    }

    /**
     * @notice Set whether or not to limit buy/sells to single per block to prevent
     * sandwitch attacks.
     * @param newLimitPerBlockTransfers new state
     * @dev Only callable by taxManager
     */
    function setLimitPerBlockTransfers(
        bool newLimitPerBlockTransfers
    ) external onlyTaxManager {
        if (newLimitPerBlockTransfers == limitPerBlockTransfers) {
            revert InvalidParameters();
        }
        limitPerBlockTransfers = newLimitPerBlockTransfers;
        emit LimitPerBlockTransfersChangeed(newLimitPerBlockTransfers);
    }

    /**
     * @notice Change the address receiving the 1% tax in WETH.
     * @param newTaxRecipient new adddress receiving the 1% tax in WETH.
     * @dev Only callable by taxManager.
     */
    function setTaxRecipient(address newTaxRecipient) external onlyTaxManager {
        if (newTaxRecipient == address(0) || taxRecipient == newTaxRecipient) {
            revert InvalidParameters();
        }
        taxRecipient = newTaxRecipient;
        emit TaxRecipientChanged(newTaxRecipient);
    }

    // *** presale interface ***

    /**
     * @notice Whitelist wallet addresses for ealry presale access.
     * @param accounts accounts to whitelist
     */
    function whitelistForPresale(
        address[] calldata accounts
    ) external onlyOwner {
        for (uint256 i = 0; i < accounts.length; ++i) {
            presaleWhitelist[accounts[i]] = true;
        }
    }

    /**
     * @notice Open presale for whitelisted users.
     * @dev Called after #whitelistForPresale.
     */
    function openPresale() external onlyOwner {
        if (presaleState != PresaleState.CLOSED) {
            revert InvalidState();
        }
        presaleState = PresaleState.OPEN_FOR_WHITELIST;
        emit PresaleOpened();
    }

    /**
     * @notice Open presale for all users.
     * @dev Called after #openPresale.
     */
    function openPublicPresale() external onlyOwner {
        if (presaleState != PresaleState.OPEN_FOR_WHITELIST) {
            revert InvalidState();
        }
        presaleState = PresaleState.OPEN_FOR_PUBLIC;
        emit PublicPresaleOpened();
    }

    /**
     * @notice Complete the presale.
     * @dev Adds 60% of collected ETH with 30% of totalSupply to Liquidity.
     * Sends the remaining 40% of collected ETH to current owner.
     * Called after #openPublicPresale.
     */
    function completePresale() external onlyOwner {
        if (presaleState != PresaleState.OPEN_FOR_PUBLIC) {
            revert InvalidState();
        }
        if (totalCommitments == 0) {
            revert NoCommittments();
        }

        presaleState = PresaleState.COMPLETED;

        uint256 amountEthForLiquidity = (totalCommitments * _SUPPLY_LIQUIDITY) /
            _SUPPLY_PRESALE;
        _addInitialLiquidityEth(
            _SUPPLY_LIQUIDITY,
            amountEthForLiquidity,
            msg.sender
        );

        _sweepEth(msg.sender);

        emit PresaleCompleted(totalCommitments);
    }

    /**
     * @notice Enable trading.
     * @param blocksDelay variable number of blocks to delay the actual start of trading, [1-5].
     * @dev Exact start is delayed a variable amount of 1-5 blocks to make it harder for automated bots.
     * Called after #completePresale.
     */
    function enableTrading(uint256 blocksDelay) external onlyOwner {
        if (_tradeableAfterBlock != type(uint256).max) {
            revert TradeAlreadyOpend();
        }
        if (blocksDelay == 0 || blocksDelay > 5) {
            revert InvalidBlockDelay();
        }
        if (presaleState != PresaleState.COMPLETED) {
            revert PresaleNotCompleted();
        }
        _tradeableAfterBlock = block.number + blocksDelay;
        emit TradingEnabled();

        // renounce ownership
        transferOwnership(address(0));
    }

    /**
     * @notice Claim callers presale tokens.
     * @dev Callable once presaleCompleted.
     */
    function claimPresale() external {
        address account = msg.sender;

        if (_isContract(account)) {
            revert NoContract();
        }
        if (presaleState != PresaleState.COMPLETED) {
            revert PresaleNotCompleted();
        }
        if (commitment[account] == 0) {
            revert NothingCommitted();
        }
        if (claimedTokens[account] != 0) {
            revert AlreadyClaimed();
        }

        uint256 amountTokens = (_SUPPLY_PRESALE * commitment[account]) /
            totalCommitments;
        claimedTokens[account] = amountTokens;
        totalClaimed += amountTokens;

        _transferFromContractBalance(account, amountTokens);

        emit PresaleClaimed(account, amountTokens);
    }

    /** @notice Returns amount of tokens to be claimed by presale participants. */
    function unclaimedSupply() external view returns (uint256) {
        return _SUPPLY_PRESALE - totalClaimed;
    }

    /**
     * @notice Returns false if trading was not enabled yet.
     * Trading can not be paused once enabled.
     */
    function isTradeOpen() external view returns (bool) {
        return block.number > _tradeableAfterBlock;
    }

    /**
     * @notice Commit ETH to presale.
     * Presale supply is claimable proportionally for all presale participants.
     * Presale has a 132 ETH hardcap and 0.6 ETH per wallet limit.
     * Users can also send ETH directly to **this** contract to participate.
     * @dev Callable once presaleOpen.
     */
    function commitToPresale() public payable {
        address account = msg.sender;
        if (_isContract(account)) {
            revert NoContract();
        }
        if (
            presaleState == PresaleState.OPEN_FOR_WHITELIST &&
            !presaleWhitelist[account]
        ) {
            revert NotWhitelistedForPresale();
        }
        if (
            presaleState != PresaleState.OPEN_FOR_WHITELIST &&
            presaleState != PresaleState.OPEN_FOR_PUBLIC
        ) {
            revert PresaleClosed();
        }

        commitment[account] += msg.value;
        totalCommitments += msg.value;

        if (totalCommitments > PRESALE_HARDCAP) {
            revert HardcapExceeded();
        }
        if (commitment[account] > PRESALE_ACCOUNT_LIMIT) {
            revert MaxAccountLimitExceeded();
        }

        emit CommitedToPresale(account, msg.value);
    }

    /**
     * @notice Threshold of how many tokens to collect from tax before
     * calling #swapTokensAndAddLiquidity.
     * @dev Depends on swapThresholdEth which can be configured by taxManager.
     * Restricted to 5% of liquidity.
     */
    function swapThresholdToken() public view returns (uint256) {
        (uint reserveToken, uint reserveWeth) = _getReserve();
        uint256 maxSwapEth = (reserveWeth * 5) / 100;
        return
            _getAmountToken(
                swapThresholdEth > maxSwapEth ? maxSwapEth : swapThresholdEth,
                reserveToken,
                reserveWeth
            );
    }

    /**
     * @notice Swap 3/4 of `amountToken` collected from tax to WETH to add to
     * liquidity and send to taxRecipient.
     */
    function _swapTokensAndAddLiquidity(uint256 amountToken) internal {
        if (
            balanceOf[address(this)] + totalClaimed <
            amountToken + _SUPPLY_PRESALE
        ) {
            return;
        }

        uint256 amountToSell = (amountToken * 3) / 4;
        uint256 amountToAddToLiquidity = amountToken - amountToSell;

        _swapForWETH(amountToSell, address(this));

        uint256 amountWethAddedToLiquidity = _addLiquidity(
            amountToAddToLiquidity,
            address(0xdead)
        );
        uint256 amountWethCollected = _sweepWeth(taxRecipient);

        emit SwappedTokensAndAddedLiquidity(
            amountToSell,
            amountToAddToLiquidity,
            amountWethAddedToLiquidity,
            amountWethCollected
        );
    }

    /**
     * @dev Enforce a one buy/sell per block limit to prevent sandwitch attacks.
     * These checks come with a gas cost tradeoff and can be configured via
     * limitPerBlockTransfers.
     * @param from sender
     * @param to receiver
     */
    function _enforceTransferLimit(address from, address to) internal {
        bool toPool = isExchangePool[to];
        bool fromPool = isExchangePool[from];
        if (fromPool && !toPool) {
            bytes32 key = keccak256(abi.encodePacked(block.number, to));
            if (_perBlock[key]) {
                revert NoSameBlockBuySell();
            }
            _perBlock[key] = true;
        } else if (!fromPool && toPool) {
            bytes32 key = keccak256(abi.encodePacked(block.number, from));
            if (_perBlock[key]) {
                revert NoSameBlockBuySell();
            }
            _perBlock[key] = true;
        }
    }
}