// File: AntiBot.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Storage.sol";

import "./IMintFactory.sol";

import "./Ownable.sol";

contract AntiBotFacet is Storage, Ownable {

    // AntiBot

    function antiBotIsActiveModifier() view internal {
        require(antiBotSettings.isActive, "ABD");
    }

    modifier antiBotIsActive() {
        antiBotIsActiveModifier();
        _;
    }

    function setIncrement(uint256 _updatedIncrement) public onlyOwner antiBotIsActive {
        antiBotSettings.increment = _updatedIncrement;
        emit UpdatedAntiBotIncrement(_updatedIncrement);
    }

    function setEndDate( uint256 _updatedEndDate) public onlyOwner antiBotIsActive {
        require(_updatedEndDate <= 48, "ED");
        antiBotSettings.endDate = _updatedEndDate;
        emit UpdatedAntiBotEndDate(_updatedEndDate);
    }

    function setInitialMaxHold( uint256 _updatedInitialMaxHold) public onlyOwner antiBotIsActive {
        antiBotSettings.initialMaxHold = _updatedInitialMaxHold;
        emit UpdatedAntiBotInitialMaxHold(_updatedInitialMaxHold);
    }

    function updateAntiBot(bool _isActive) public onlyOwner {
        require(!marketInit, "AMIE");
        antiBotSettings.isActive = _isActive;
        emit UpdatedAntiBotActiveStatus(_isActive);
    }

    function antiBotCheck(uint256 amount, address receiver) public returns(bool) {
        // restrict it to being only called by registered tokens
        require(IMintFactory(factory).tokenIsRegistered(address(this)));
        require(marketInit, "AMIE");
        if(block.timestamp > marketInitBlockTime + (antiBotSettings.endDate * 1 hours)) {
            antiBotSettings.isActive = false;
            return true;
        }

        antiBotBalanceTracker[receiver] += amount;
        uint256 userAntiBotBalance = antiBotBalanceTracker[receiver];
        uint256 maxAntiBotBalance = ((block.number - antiBotSettings.startBlock) * antiBotSettings.increment) + antiBotSettings.initialMaxHold;

        require((userAntiBotBalance <= maxAntiBotBalance), "ABMSA");
        return true;
    }

    // MaxBalanceAfterBuy
   
    function addMaxBalanceWhitelistedAddress(address _address) public onlyOwner {
        require(taxSettings.maxBalanceAfterBuy, "AMBABD");
        maxBalanceWhitelist[_address] = true;
        emit AddedMaxBalanceWhitelistAddress(_address);
    }

    function removeMaxBalanceWhitelistedAddress(address _address) public onlyOwner {
        require(taxSettings.maxBalanceAfterBuy, "AMBABD");
        maxBalanceWhitelist[_address] = false;
        emit RemovedMaxBalanceWhitelistAddress(_address);
    }

    function updateMaxBalanceWhitelistBatch(address[] calldata _updatedAddresses, bool _isMaxBalanceWhitelisted) public onlyOwner {
        require(taxSettings.maxBalanceAfterBuy, "AMBABD");
        for(uint i = 0; i < _updatedAddresses.length; i++) {
            maxBalanceWhitelist[_updatedAddresses[i]] = _isMaxBalanceWhitelisted;
            if(_isMaxBalanceWhitelisted) {
                emit AddedMaxBalanceWhitelistAddress(_updatedAddresses[i]);
            } else {
                emit RemovedMaxBalanceWhitelistAddress(_updatedAddresses[i]);
            }
        }
    }

    function isMaxBalanceWhitelisted(address _address) public view returns (bool) {
        return maxBalanceWhitelist[_address];
    }

    function updateMaxBalanceAfterBuy(uint256 _updatedMaxBalanceAfterBuy) public onlyOwner {
        require(taxSettings.maxBalanceAfterBuy, "AMBABD");
        maxBalanceAfterBuy = _updatedMaxBalanceAfterBuy;
        emit UpdatedMaxBalanceAfterBuy(_updatedMaxBalanceAfterBuy);
    }

    function maxBalanceAfterBuyCheck(uint256 amount, address receiver) public view returns(bool) {
        if(maxBalanceWhitelist[receiver]) {
            return true;
        }
        require(taxSettings.maxBalanceAfterBuy);
        uint256 receiverBalance;
        if(taxSettings.holderTax) {
            receiverBalance = _rOwned[receiver];
        } else {
            receiverBalance = _tOwned[receiver];
        }
        receiverBalance += amount;
        require(receiverBalance <= maxBalanceAfterBuy, "MBAB");
        return true;
    }

    // SwapWhitelist

    function addSwapWhitelistedAddress(address _address) public onlyOwner {
        require(swapWhitelistingSettings.isActive, "ASWD");
        swapWhitelist[_address] = true;
        emit AddedSwapWhitelistAddress(_address);
    }

    function removeSwapWhitelistedAddress(address _address) public onlyOwner {
        require(swapWhitelistingSettings.isActive, "ASWD");
        swapWhitelist[_address] = false;
        emit RemovedSwapWhitelistAddress(_address);
    }

    function updateSwapWhitelistBatch(address[] calldata _updatedAddresses, bool _isSwapWhitelisted) public onlyOwner {
        require(swapWhitelistingSettings.isActive, "ASWD");
        for(uint i = 0; i < _updatedAddresses.length; i++) {
            swapWhitelist[_updatedAddresses[i]] = _isSwapWhitelisted;
            if(_isSwapWhitelisted) {
                emit AddedSwapWhitelistAddress(_updatedAddresses[i]);
            } else {
                emit RemovedSwapWhitelistAddress(_updatedAddresses[i]);
            }
        }
    }

    function isSwapWhitelisted(address _address) public view returns (bool) {
        return swapWhitelist[_address];
    }

    function setSwapWhitelistEndDate( uint256 _updatedEndDate) public onlyOwner {
        require(swapWhitelistingSettings.isActive, "ASWD");
        require(_updatedEndDate <= 48, "ED");
        swapWhitelistingSettings.endDate = _updatedEndDate;
        emit UpdatedSwapWhitelistingEndDate(_updatedEndDate);
    }

    function updateSwapWhitelisting(bool _isActive) public onlyOwner {
        require(!marketInit, "AMIE");
        swapWhitelistingSettings.isActive = _isActive;
        emit UpdatedSwapWhitelistingActiveStatus(_isActive);
    }

    function swapWhitelistingCheck(address receiver) public returns(bool) {
        require(marketInit, "AMIE");
        if(block.timestamp > marketInitBlockTime + (swapWhitelistingSettings.endDate * 1 hours)) {
            swapWhitelistingSettings.isActive = false;
            return true;
        }
        require(swapWhitelist[receiver], "SWL");
        return true;
    }
}
// File: BuyBackWallet.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Ownable.sol";

import "./ITaxToken.sol";
import "./ITaxHelper.sol";

import "./IMintFactory.sol";

contract BuyBackWallet is Ownable{
    

    ITaxToken public token; 
    IMintFactory public factory;
    uint256 private threshold;

    event UpdatedThreshold(uint256 _newThreshold);
    event ETHtoTaxHelper(uint256 amount);


    constructor(address _factory, address _token, uint256 _newThreshold) {
        token = ITaxToken(_token);
        factory = IMintFactory(_factory);
        threshold = _newThreshold;
        emit UpdatedThreshold(_newThreshold);
        transferOwnership(_token);
    }
    
    function checkBuyBackTrigger() public view returns (bool) {
        return address(this).balance > threshold;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function sendEthToTaxHelper() external returns (uint256) {
        uint index = token.taxHelperIndex();
        require(msg.sender == factory.getTaxHelperAddress(index), "RA");
        uint256 amount = address(this).balance;
        (bool sent,) = msg.sender.call{value: amount}("");
        require(sent, "Failed to send Ether");
        emit ETHtoTaxHelper(amount);
        return amount;
    }

    function updateThreshold(uint256 _newThreshold) external onlyOwner {
        threshold = _newThreshold;
        emit UpdatedThreshold(_newThreshold);
    }

    function getThreshold() external view returns (uint256) {
        return threshold;
    }

    receive() payable external {
    }
} 
// File: Constructor.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./IERC20.sol";
import "./ITaxHelper.sol";
import "./IMintFactory.sol";
import "./IFacetHelper.sol";
import "./IBuyBackWallet.sol";
import "./ILPWallet.sol";
import "./ISettings.sol";
import "./IWallets.sol";

import "./Storage.sol";
import "./Ownable.sol";

contract ConstructorFacet is Storage, Ownable {
    
    struct ConstructorParams {
        string name_; 
        string symbol_; 
        uint8 decimals_; 
        address creator_; 
        uint256 tTotal_;
        uint256 _maxTax;
        TaxSettings _settings;
        TaxSettings _lockedSettings;
        Fees _fees;
        address _transactionTaxWallet;
        CustomTax[] _customTaxes;
        uint256 lpWalletThreshold;
        uint256 buyBackWalletThreshold;
        uint256 _taxHelperIndex;
        address admin_; 
        address recoveryAdmin_; 
        bool isLossless_;
        AntiBotSettings _antiBotSettings;
        uint256 _maxBalanceAfterBuy;
        SwapWhitelistingSettings _swapWhitelistingSettings;
    }

    function constructorHandler(ConstructorParams calldata params, address _factory) external {
        require(IMintFactory(_factory).tokenGeneratorIsAllowed(msg.sender), "RA");
        require(params.creator_ != address(0), "ZA");
        require(params._transactionTaxWallet != address(0), "ZA");
        require(params.admin_ != address(0), "ZA");
        require(params.recoveryAdmin_ != address(0), "ZA");
        require(_factory != address(0), "ZA");

        _name = params.name_;
        _symbol = params.symbol_;
        _decimals = params.decimals_;
        _creator = params.creator_;
        _isExcluded[params.creator_] = true;
        _excluded.push(params.creator_);
        emit ExcludedAccount(_creator);
        // Lossless
        isLosslessOn = params.isLossless_;
        admin = params.admin_;
        emit AdminChanged(address(0), admin);
        recoveryAdmin = params.recoveryAdmin_;
        emit RecoveryAdminChanged(address(0), recoveryAdmin);
        timelockPeriod = 7 days;
        lossless = ILosslessController(IMintFactory(_factory).getLosslessController());
        _isExcluded[address(lossless)] = true;
        _excluded.push(address(lossless));
        emit ExcludedAccount(address(lossless));
        // Tax Settings
        require(params._maxTax <= MaxTax, "MT");
        MaxTax = params._maxTax;
        taxSettings = params._settings;
        emit UpdatedSettings(taxSettings);
        isLocked = params._lockedSettings;
        isLocked.holderTax = true;
        if(taxSettings.holderTax) {
            taxSettings.canMint = false;
            isLocked.canMint = true;
        }
        emit UpdatedLockedSettings(isLocked);
        fees = params._fees;
        emit UpdatedTaxFees(fees);
        require(params._customTaxes.length < MaxCustom + 1, "MCT");
        for(uint i = 0; i < params._customTaxes.length; i++) {
            require(params._customTaxes[i].wallet != address(0));
            customTaxes.push(params._customTaxes[i]);
        }
        emit UpdatedCustomTaxes(customTaxes);
        customTaxLength = params._customTaxes.length;
        transactionTaxWallet = params._transactionTaxWallet;
        emit UpdatedTransactionTaxAddress(transactionTaxWallet);
        // Factory, Wallets, Pair Address
        factory = _factory;
        taxHelperIndex = params._taxHelperIndex;
        emit UpdatedTaxHelperIndex(taxHelperIndex);
        address taxHelper = IMintFactory(factory).getTaxHelperAddress(taxHelperIndex);
        pairAddress = ITaxHelper(taxHelper).createLPToken();
        addLPToken(pairAddress);
        address wallets = IFacetHelper(IMintFactory(factory).getFacetHelper()).getWalletsFacet(); 
        buyBackWallet = IWalletsFacet(wallets).createBuyBackWallet(factory, address(this), params.buyBackWalletThreshold);
        lpWallet = IWalletsFacet(wallets).createLPWallet(factory, address(this), params.lpWalletThreshold);
        // Total Supply and other info
        _rTotal = (MAX - (MAX % params.tTotal_));
        _rOwned[params.creator_] = _rTotal;
        DENOMINATOR = 10000;
        _isExcluded[taxHelper] = true;
        _excluded.push(taxHelper);
        emit ExcludedAccount(taxHelper);
        require(checkMaxTax(true), "BF");
        require(checkMaxTax(false), "SF");
        transferOwnership(params.creator_);
        _mintInitial(params.creator_, params.tTotal_);
        // AntiBot Settings
        require(params._antiBotSettings.endDate <= 48, "ED");
        require(params._swapWhitelistingSettings.endDate <= 48, "ED");
        antiBotSettings = params._antiBotSettings;
        emit UpdatedAntiBotSettings(antiBotSettings);
        maxBalanceAfterBuy = params._maxBalanceAfterBuy;
        emit UpdatedMaxBalanceAfterBuy(maxBalanceAfterBuy);
        swapWhitelistingSettings = params._swapWhitelistingSettings;
        emit UpdatedSwapWhitelistingSettings(swapWhitelistingSettings);
        emit TokenCreated(_name, _symbol, _decimals, _tTotal, _rTotal);
    }

    function _mintInitial(address account, uint256 amount) internal virtual {
        _tTotal += amount;
        _tOwned[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    function checkMaxTax(bool isBuy) internal view returns (bool) {
        uint256 totalTaxes;
        if(isBuy) {
            totalTaxes += fees.transactionTax.buy;
            totalTaxes += fees.holderTax;
            for(uint i = 0; i < customTaxes.length; i++) {
                totalTaxes += customTaxes[i].fee.buy;
            }
        } else {
            totalTaxes += fees.transactionTax.sell;
            totalTaxes += fees.lpTax;
            totalTaxes += fees.holderTax;
            totalTaxes += fees.buyBackTax;
            for(uint i = 0; i < customTaxes.length; i++) {
                totalTaxes += customTaxes[i].fee.sell;
            }
        }
        if(totalTaxes <= MaxTax) {
            return true;
        }
        return false;
    }


    function addLPToken(address _newLPToken) internal {
        lpTokens[_newLPToken] = true;
        emit AddedLPToken(_newLPToken);
    }
}
// File: Context.sol



// SPDX-License-Identifier: MIT

// File @openzeppelin/contracts/utils/Context.sol@v4.0.0

pragma solidity 0.8.17;

/*
 * @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) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: EnumerableSet.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)

pragma solidity 0.8.17;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastvalue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastvalue;
                // Update the index for the moved value
                set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        return _values(set._inner);
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly {
            result := store
        }

        return result;
    }
}
// File: ERC20.sol



// SPDX-License-Identifier: MIT

// File @openzeppelin/contracts/token/ERC20/ERC20.sol@v4.0.0

pragma solidity 0.8.17;

import "./IERC20.sol";
import "./Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The defaut value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overloaded;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, _msgSender(), currentAllowance - amount);

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);

        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        _balances[account] = accountBalance - amount;
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        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);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

// File: FacetHelper.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Ownable.sol";

contract FacetHelper is Ownable{

    event AddedFacet(address _newFacet);
    event AddedSelector(address _facet, bytes4 _sig);
    event RemovedSelector(bytes4 _sig);
    event ResetStorage();

    event UpdatedSettingsFacet(address _newAddress);
    event UpdatedLosslessFacet(address _newAddress);
    event UpdatedTaxFacet(address _newAddress);
    event UpdatedConstructorFacet(address _newAddress);
    event UpdatedWalletsFacet(address _newAddress);
    event UpdatedAntiBotFacet(address _newAddress);
    event UpdatedMulticallFacet(address _newAddress);

    struct Facets {
        address Settings;
        address Lossless;
        address Tax;
        address Constructor;
        address Wallets;
        address AntiBot;
        address Multicall;
    }

    Facets public facets;

    mapping(bytes4 => address) public selectorToFacet;
    bytes4[] public selectorsList;
    mapping(address => bool) public isFacet;
    address[] public facetsList;

    function addFacet(address _newFacet) public onlyOwner {
        isFacet[_newFacet] = true;
        facetsList.push(_newFacet);
        emit AddedFacet(_newFacet);
    }

    function addSelector(address _facet, bytes4 _sig) public onlyOwner {
        require(selectorToFacet[_sig] == address(0));
        // require(isFacet[_facet]);
        selectorToFacet[_sig] = _facet;
        selectorsList.push(_sig);
        emit AddedSelector(_facet, _sig);
    }

    // Removing of the selectors occurs during resetFacetStorage();
    // it is easier to reset and rebuild using the script when deploying and updating the facets
    // function removeSelector(bytes4 _sig) public onlyOwner {
    //     selectorToFacet[_sig] = address(0);
    //     emit RemovedSelector(_sig);
    // }    

    function getFacetAddressFromSelector(bytes4 _sig) public view returns (address) {
        return selectorToFacet[_sig];
    }

    function getFacetByIndex(uint256 _index) public view returns(address) {
        return facetsList[_index];
    }

    function resetFacetStorage() public onlyOwner {
        for(uint i = 0; i < selectorsList.length; i++) {
            bytes4 sig = selectorsList[i];
            selectorToFacet[sig] = address(0);
        }
        delete selectorsList;

        for(uint i = 0; i < facetsList.length; i++) {
            address facet = facetsList[i];
            isFacet[facet] = false;
        }
        delete facetsList;

        emit ResetStorage();
    }

        // Facet getters and setters

    function getSettingsFacet() public view returns (address) {
        return facets.Settings;
    }

    function updateSettingsFacet(address _newSettingsAddress) public onlyOwner {
        facets.Settings = _newSettingsAddress;
        emit UpdatedSettingsFacet(_newSettingsAddress);
    }

    function getLosslessFacet() public view returns (address) {
        return facets.Lossless;
    }

    function updateLosslessFacet(address _newLosslessAddress) public onlyOwner {
        facets.Lossless = _newLosslessAddress;
        emit UpdatedLosslessFacet(_newLosslessAddress);
    }

    function getTaxFacet() public view returns (address) {
        return facets.Tax;
    }

    function updateTaxFacet(address _newTaxAddress) public onlyOwner {
        facets.Tax = _newTaxAddress;
        emit UpdatedTaxFacet(_newTaxAddress);
    }

    function getConstructorFacet() public view returns (address) {
        return facets.Constructor;
    }

    function updateConstructorFacet(address _newConstructorAddress) public onlyOwner {
        facets.Constructor = _newConstructorAddress;
        emit UpdatedConstructorFacet(_newConstructorAddress);
    }

    function getWalletsFacet() public view returns (address) {
        return facets.Wallets;
    }

    function updateWalletsFacet(address _newWalletsAddress) public onlyOwner {
        facets.Wallets = _newWalletsAddress;
        emit UpdatedWalletsFacet(_newWalletsAddress);
    }

    function getAntiBotFacet() public view returns (address) {
        return facets.AntiBot;
    }

    function updateAntiBotFacet(address _newAntiBotAddress) public onlyOwner {
        facets.AntiBot = _newAntiBotAddress;
        emit UpdatedAntiBotFacet(_newAntiBotAddress);
    }

    function getMulticallFacet() public view returns (address) {
        return facets.Multicall;
    }

    function updateMulticallFacet(address _newWalletsAddress) public onlyOwner {
        facets.Multicall = _newWalletsAddress;
        emit UpdatedMulticallFacet(_newWalletsAddress);
    }
} 
// File: FeeHelper.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Ownable.sol";

contract FeeHelper is Ownable{
    
    struct Settings {
        uint256 GENERATOR_FEE;
        uint256 FEE; 
        uint256 DENOMINATOR;
        address payable FEE_ADDRESS;
    }
    
    Settings public SETTINGS;
    
    constructor() {
        SETTINGS.GENERATOR_FEE = 0;
        SETTINGS.FEE = 100;
        SETTINGS.DENOMINATOR = 10000;
        SETTINGS.FEE_ADDRESS = payable(msg.sender);
    }

    function getGeneratorFee() external view returns(uint256) {
        return SETTINGS.GENERATOR_FEE;
    }
    
    function getFee() external view returns(uint256) {
        return SETTINGS.FEE;
    }

    function getFeeDenominator() external view returns(uint256) {
        return SETTINGS.DENOMINATOR;
    }

    function setGeneratorFee(uint256 _fee) external onlyOwner {
        SETTINGS.GENERATOR_FEE = _fee;
    }
    
    function setFee(uint _fee) external onlyOwner {
        SETTINGS.FEE = _fee;
    }
    
    function getFeeAddress() external view returns(address) {
        return SETTINGS.FEE_ADDRESS;
    }
    
    function setFeeAddress(address payable _feeAddress) external onlyOwner {
        SETTINGS.FEE_ADDRESS = _feeAddress;
    }
} 
// File: FullMath.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
   /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
    uint256 a,
    uint256 b,
    uint256 denominator
) internal pure returns (uint256 result) {
    // 512-bit multiply [prod1 prod0] = a * b
    // Compute the product mod 2**256 and mod 2**256 - 1
    // then use the Chinese Remainder Theorem to reconstruct
    // the 512 bit result. The result is stored in two 256
    // variables such that product = prod1 * 2**256 + prod0
    uint256 prod0; // Least significant 256 bits of the product
    uint256 prod1; // Most significant 256 bits of the product
    assembly {
        let mm := mulmod(a, b, not(0))
        prod0 := mul(a, b)
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
    }

    // Handle non-overflow cases, 256 by 256 division
    if (prod1 == 0) {
        require(denominator > 0);
        assembly {
            result := div(prod0, denominator)
        }
        return result;
    }

    // Make sure the result is less than 2**256.
    // Also prevents denominator == 0
    require(denominator > prod1);

    ///////////////////////////////////////////////
    // 512 by 256 division.
    ///////////////////////////////////////////////

    // Make division exact by subtracting the remainder from [prod1 prod0]
    // Compute remainder using mulmod
    uint256 remainder;
    assembly {
        remainder := mulmod(a, b, denominator)
    }
    // Subtract 256 bit number from 512 bit number
    assembly {
        prod1 := sub(prod1, gt(remainder, prod0))
        prod0 := sub(prod0, remainder)
    }

    // Factor powers of two out of denominator
    // Compute largest power of two divisor of denominator.
    // Always >= 1.
    unchecked {
        uint256 twos = (type(uint256).max - denominator + 1) & denominator;
        // Divide denominator by power of two
        assembly {
            denominator := div(denominator, twos)
        }

        // Divide [prod1 prod0] by the factors of two
        assembly {
            prod0 := div(prod0, twos)
        }
        // Shift in bits from prod1 into prod0. For this we need
        // to flip `twos` such that it is 2**256 / twos.
        // If twos is zero, then it becomes one
        assembly {
            twos := add(div(sub(0, twos), twos), 1)
        }
        prod0 |= prod1 * twos;

        // Invert denominator mod 2**256
        // Now that denominator is an odd number, it has an inverse
        // modulo 2**256 such that denominator * inv = 1 mod 2**256.
        // Compute the inverse by starting with a seed that is correct
        // correct for four bits. That is, denominator * inv = 1 mod 2**4
        uint256 inv = (3 * denominator) ^ 2;
        // Now use Newton-Raphson iteration to improve the precision.
        // Thanks to Hensel's lifting lemma, this also works in modular
        // arithmetic, doubling the correct bits in each step.
        inv *= 2 - denominator * inv; // inverse mod 2**8
        inv *= 2 - denominator * inv; // inverse mod 2**16
        inv *= 2 - denominator * inv; // inverse mod 2**32
        inv *= 2 - denominator * inv; // inverse mod 2**64
        inv *= 2 - denominator * inv; // inverse mod 2**128
        inv *= 2 - denominator * inv; // inverse mod 2**256

        // Because the division is now exact we can divide by multiplying
        // with the modular inverse of denominator. This will give us the
        // correct result modulo 2**256. Since the precoditions guarantee
        // that the outcome is less than 2**256, this is the final result.
        // We don't need to compute the high bits of the result and prod1
        // is no longer required.
        result = prod0 * inv;
        return result;
        }
    }

    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        result = mulDiv(a, b, denominator);
        if (mulmod(a, b, denominator) > 0) {
            require(result < type(uint256).max);
            result++;
        }
    }
}
// File: IBuyBackWallet.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface IBuyBackWallet {

    function checkBuyBackTrigger() external view returns (bool);

    function getBalance() external view returns (uint256);

    function sendEthToTaxHelper() external returns(uint256);

    function updateThreshold(uint256 _newThreshold) external;

    function getThreshold() external view returns (uint256);
}
// File: IERC20.sol



// SPDX-License-Identifier: MIT

// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.0.0

pragma solidity 0.8.17;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: IFacetHelper.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface IFacetHelper {

    function addFacet(address _newFacet) external;

    function addSelector(address _facet, bytes4 _sig) external;

    function removeSelector(bytes4 _sig) external;

    function getFacetAddressFromSelector(bytes4 _sig) external view returns (address);

    function getSettingsFacet() external view returns (address);

    function updateSettingsFacet(address _newSettingsAddress) external;

    function getTaxFacet() external view returns (address);

    function updateTaxFacet(address _newTaxesAddress) external;

    function getLosslessFacet() external view returns (address);

    function updateLosslessFacet(address _newLosslessAddress) external;

    function getConstructorFacet() external view returns (address);

    function updateConstructorFacet(address _newConstructorAddress) external;

    function getWalletsFacet() external view returns (address);

    function updateWalletsFacet(address _newWalletsAddress) external;

    function getAntiBotFacet() external view returns (address);

    function updateAntiBotFacet(address _newWalletsAddress) external;

    function getMulticallFacet() external view returns (address);

    function updateMulticallFacet(address _newWalletsAddress) external;
    
}
// File: IFeeHelper.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface IFeeHelper {
    function getFee() view external returns(uint256);
    
    function getFeeDenominator() view external returns(uint256);
    
    function setFee(uint _fee) external;
    
    function getFeeAddress() view external returns(address);
    
    function setFeeAddress(address payable _feeAddress) external;

    function getGeneratorFee() view external returns(uint256);

    function setGeneratorFee(uint256 _fee) external;
}
// File: ILosslessController.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;


interface ILosslessController {
    
    function pause() external;
    function unpause() external;
    function setAdmin(address _newAdmin) external;
    function setRecoveryAdmin(address _newRecoveryAdmin) external;

    function beforeTransfer(address _sender, address _recipient, uint256 _amount) external;
    function beforeTransferFrom(address _msgSender, address _sender, address _recipient, uint256 _amount) external;
    function beforeApprove(address _sender, address _spender, uint256 _amount) external;
    function beforeIncreaseAllowance(address _msgSender, address _spender, uint256 _addedValue) external;
    function beforeDecreaseAllowance(address _msgSender, address _spender, uint256 _subtractedValue) external;
    function beforeMint(address _to, uint256 _amount) external;
    function beforeBurn(address _account, uint256 _amount) external;
    function afterTransfer(address _sender, address _recipient, uint256 _amount) external;


    event AdminChange(address indexed _newAdmin);
    event RecoveryAdminChange(address indexed _newAdmin);
}

// File: ILPWallet.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface ILPWallet {

    function checkLPTrigger() external view returns (bool);

    function getBalance() external view returns (uint256);

    function sendEthToTaxHelper() external returns(uint256);

    function transferBalanceToTaxHelper() external;

    function updateThreshold(uint256 _newThreshold) external;

    function getThreshold() external view returns (uint256);
}
// File: IMintFactory.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface IMintFactory {

    struct TaxHelper {
        string Name;
        address Address;
        uint Index;
    }

    function addTaxHelper(string calldata _name, address _address) external;

    function updateTaxHelper(uint _index, address _address) external;

    function getTaxHelperAddress(uint _index) external view returns(address);

    function getTaxHelpersDataByIndex(uint _index) external view returns(TaxHelper memory);

    function registerToken (address _tokenOwner, address _tokenAddress) external;

    function tokenIsRegistered(address _tokenAddress) external view returns (bool);

    function tokenGeneratorsLength() external view returns (uint256);

    function tokenGeneratorIsAllowed(address _tokenGenerator) external view returns (bool);

    function getFacetHelper() external view returns (address);

    function updateFacetHelper(address _newFacetHelperAddress) external;

    function getFeeHelper() external view returns (address);

    function updateFeeHelper(address _newFeeHelperAddress) external;
    
    function getLosslessController() external view returns (address);

    function updateLosslessController(address _newLosslessControllerAddress) external;
}
// File: ISettings.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface ISettingsFacet {

    function getFacetAddressFromSelector(bytes4 _sig) external view returns (address);
    function createBuyBackWallet(address _factory, address _token) external returns (address);
    function createLPWallet(address _factory, address _token) external returns (address);
}
// File: ITaxHelper.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface ITaxHelper {

    function initiateBuyBackTax(
        address _token,
        address _wallet
    ) external returns (bool);

    function initiateLPTokenTax(        
        address _token,
        address _wallet
    ) external returns (bool);

    function lpTokenHasReserves(address _lpToken) external view returns (bool);

    function createLPToken() external returns (address lpToken);
}
// File: ITaxToken.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./IERC20.sol";

interface ITaxToken is IERC20 {

    function taxHelperIndex()external view returns(uint);

    function buyBackBurn(uint256 _amount) external;

    function owner() external view returns (address);


}
// File: IUniswapV2Factory.sol



pragma solidity >=0.5.0;

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;
}
// File: IUniswapV2Pair.sol



pragma solidity >=0.5.0;

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;
}
// File: IUniswapV2Router01.sol



pragma solidity >=0.6.2;

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);
}
// File: IUniswapV2Router02.sol



pragma solidity >=0.6.2;

import './IUniswapV2Router01.sol';

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: IWallets.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

interface IWalletsFacet {

    function createBuyBackWallet(address _factory, address _token, uint256 _newThreshold) external returns (address);
    function createLPWallet(address _factory, address _token, uint256 _newThreshold) external returns (address);

    function updateBuyBackWalletThreshold(uint256 _newThreshold) external;

    function updateLPWalletThreshold(uint256 _newThreshold) external;
}
// File: Lossless.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Storage.sol";

import "./Ownable.sol";

contract LosslessFacet is Storage, Ownable {
    function onlyRecoveryAdminCheck() internal view {
        require(_msgSender() == recoveryAdmin, "LRA");
    }

    modifier onlyRecoveryAdmin() {
        onlyRecoveryAdminCheck();
        _;
    }

    // --- LOSSLESS management ---

    function getAdmin() external view returns (address) {
        return admin;
    }

    function setLosslessAdmin(address newAdmin) external onlyRecoveryAdmin {
        require(newAdmin != address(0), "LZ");
        emit AdminChanged(admin, newAdmin);
        admin = newAdmin;
    }

    function transferRecoveryAdminOwnership(address candidate, bytes32 keyHash) external onlyRecoveryAdmin {
        require(candidate != address(0), "LZ");
        recoveryAdminCandidate = candidate;
        recoveryAdminKeyHash = keyHash;
        emit RecoveryAdminChangeProposed(candidate);
    }

    function acceptRecoveryAdminOwnership(bytes memory key) external {
        require(_msgSender() == recoveryAdminCandidate, "LC");
        require(keccak256(key) == recoveryAdminKeyHash, "LIK");
        emit RecoveryAdminChanged(recoveryAdmin, recoveryAdminCandidate);
        recoveryAdmin = recoveryAdminCandidate;
    }

    function proposeLosslessTurnOff() external onlyRecoveryAdmin {
        losslessTurnOffTimestamp = block.timestamp + timelockPeriod;
        isLosslessTurnOffProposed = true;
        emit LosslessTurnOffProposed(losslessTurnOffTimestamp);
    }

    function executeLosslessTurnOff() external onlyRecoveryAdmin {
        require(isLosslessTurnOffProposed, "LTNP");
        require(losslessTurnOffTimestamp <= block.timestamp, "LTL");
        isLosslessOn = false;
        isLosslessTurnOffProposed = false;
        emit LosslessTurnedOff();
    }

    function executeLosslessTurnOn() external onlyRecoveryAdmin {
        isLosslessTurnOffProposed = false;
        isLosslessOn = true;
        emit LosslessTurnedOn();
    }
}
// File: LPWallet.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.


pragma solidity 0.8.17;

import "./Ownable.sol";

import "./ITaxToken.sol";
import "./ITaxHelper.sol";

import "./IMintFactory.sol";


contract LPWallet is Ownable{

    ITaxToken public token;
    IMintFactory public factory;
    uint256 private threshold;

    event UpdatedThreshold(uint256 _newThreshold);
    event ETHtoTaxHelper(uint256 amount);
    event TransferBalancetoTaxHelper(uint256 tokenBalance);

    constructor(address _factory, address _token, uint256 _newThreshold) {
        token = ITaxToken(_token);
        factory = IMintFactory(_factory);
        threshold = _newThreshold;
        emit UpdatedThreshold(_newThreshold);
        transferOwnership(_token);
    }
    
    function checkLPTrigger() public view returns (bool) {
        return address(this).balance > threshold;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function sendEthToTaxHelper() external returns (uint256) {
        uint index = token.taxHelperIndex();
        require(msg.sender == factory.getTaxHelperAddress(index), "RA");
        uint256 amount = address(this).balance;
        (bool sent,) = msg.sender.call{value: amount}("");
        require(sent, "Failed to send Ether");
        emit ETHtoTaxHelper(amount);
        return amount;
    }

    function transferBalanceToTaxHelper() external {
        uint index = token.taxHelperIndex();
        require(msg.sender == factory.getTaxHelperAddress(index));
        uint256 tokenBalance = token.balanceOf(address(this));
        token.transfer(msg.sender, tokenBalance);
        emit TransferBalancetoTaxHelper(tokenBalance);
    }

    function updateThreshold(uint256 _newThreshold) external onlyOwner {
        threshold = _newThreshold;
        emit UpdatedThreshold(_newThreshold);
    }

    function getThreshold() external view returns (uint256) {
        return threshold;
    }

    receive() payable external {
    }


} 
// File: MintFactory.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.


// This contract logs all tokens on the platform

pragma solidity 0.8.17;

import "./EnumerableSet.sol";
import "./Ownable.sol";


contract MintFactory is Ownable {
    using EnumerableSet for EnumerableSet.AddressSet;
    
    EnumerableSet.AddressSet private tokens;
    EnumerableSet.AddressSet private tokenGenerators;

    struct TaxHelper {
        string Name;
        address Address;
        uint256 Index;
    }

    mapping(uint => TaxHelper) private taxHelpersData;
    address[] private taxHelpers;
     
    mapping(address => address[]) private tokenOwners;

    address private FacetHelper;
    address private FeeHelper;
    address private LosslessController;

    event TokenRegistered(address tokenOwner, address tokenContract);
    event AllowTokenGenerator(address _address, bool _allow);

    event AddedTaxHelper(string _name, address _address, uint256 _index);
    event UpdatedTaxHelper(address _newAddress, uint256 _index);

    event UpdatedFacetHelper(address _newAddress);
    event UpdatedFeeHelper(address _newAddress);
    event UpdatedLosslessController(address _newAddress);
    
    function adminAllowTokenGenerator (address _address, bool _allow) public onlyOwner {
        if (_allow) {
            tokenGenerators.add(_address);
        } else {
            tokenGenerators.remove(_address);
        }
        emit AllowTokenGenerator(_address, _allow);
    }

    function addTaxHelper(string calldata _name, address _address) public onlyOwner {
        uint256 index = taxHelpers.length;
        TaxHelper memory newTaxHelper;
        newTaxHelper.Name = _name;
        newTaxHelper.Address = _address;
        newTaxHelper.Index = index;
        taxHelpersData[index] = newTaxHelper;
        taxHelpers.push(_address);
        emit AddedTaxHelper(_name, _address, index);
    }

    function updateTaxHelper(uint256 _index, address _address) public onlyOwner {
        taxHelpersData[_index].Address = _address;
        taxHelpers[_index] = _address;
        emit UpdatedTaxHelper(_address, _index);
    }

    function getTaxHelperAddress(uint256 _index) public view returns(address){
        return taxHelpers[_index];
    }

    function getTaxHelpersDataByIndex(uint256 _index) public view returns(TaxHelper memory) {
        return taxHelpersData[_index];
    }
    
    /**
     * @notice called by a registered tokenGenerator upon token creation
     */
    function registerToken (address _tokenOwner, address _tokenAddress) public {
        require(tokenGenerators.contains(msg.sender), 'FORBIDDEN');
        tokens.add(_tokenAddress);
        tokenOwners[_tokenOwner].push(_tokenAddress);
        emit TokenRegistered(_tokenOwner, _tokenAddress);
    }

     /**
     * @notice gets a token at index registered under a user address
     * @return token addresses registered to the user address
     */
     function getTokenByOwnerAtIndex(address _tokenOwner, uint256 _index) external view returns(address) {
         return tokenOwners[_tokenOwner][_index];
     }
     
     /**
     * @notice gets the total of tokens registered under a user address
     * @return uint total of token addresses registered to the user address
     */
     
     function getTokensLengthByOwner(address _tokenOwner) external view returns(uint256) {
         return tokenOwners[_tokenOwner].length;
     }
    
    /**
     * @notice Number of allowed tokenGenerators
     */
    function tokenGeneratorsLength() external view returns (uint256) {
        return tokenGenerators.length();
    }
    
    /**
     * @notice Gets the address of a registered tokenGenerator at specified index
     */
    function tokenGeneratorAtIndex(uint256 _index) external view returns (address) {
        return tokenGenerators.at(_index);
    }

    /**
     * @notice returns true if user is allowed to generate tokens
     */
    function tokenGeneratorIsAllowed(address _tokenGenerator) external view returns (bool) {
        return tokenGenerators.contains(_tokenGenerator);
    }
    
    /**
     * @notice returns true if the token address was generated by the Unicrypt token platform
     */
    function tokenIsRegistered(address _tokenAddress) external view returns (bool) {
        return tokens.contains(_tokenAddress);
    }
    
    /**
     * @notice The length of all tokens on the platform
     */
    function tokensLength() external view returns (uint256) {
        return tokens.length();
    }
    
    /**
     * @notice gets a token at a specific index. Although using Enumerable Set, since tokens are only added and not removed, indexes will never change
     * @return the address of the token contract at index
     */
    function tokenAtIndex(uint256 _index) external view returns (address) {
        return tokens.at(_index);
    }

    // Helpers and Controllers
    
    function getFacetHelper() public view returns (address) {
        return FacetHelper;
    }

    function updateFacetHelper(address _newFacetHelperAddress) public onlyOwner {
        require(_newFacetHelperAddress != address(0));
        FacetHelper = _newFacetHelperAddress;
        emit UpdatedFacetHelper(_newFacetHelperAddress);
    }

    function getFeeHelper() public view returns (address) {
        return FeeHelper;
    }

    function updateFeeHelper(address _newFeeHelperAddress) public onlyOwner {
        require(_newFeeHelperAddress != address(0));
        FeeHelper = _newFeeHelperAddress;
        emit UpdatedFeeHelper(_newFeeHelperAddress);
    }

    function getLosslessController() public view returns (address) {
        return LosslessController;
    }

    function updateLosslessController(address _newLosslessControllerAddress) public onlyOwner {
        require(_newLosslessControllerAddress != address(0));
        LosslessController = _newLosslessControllerAddress;
        emit UpdatedLosslessController(_newLosslessControllerAddress);
    }
}
// File: MintGenerator.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.


// This contract generates Token01 contracts and registers them in the TokenFactory.
// Ideally you should not interact with this contract directly, and use the Unicrypt token app instead so warnings can be shown where necessary.

pragma solidity 0.8.17;


import "./IERC20.sol";
import "./Ownable.sol";

import "./TaxToken.sol";

import "./IMintFactory.sol";
import "./IFeeHelper.sol";

contract MintGenerator is Ownable {
    
    uint256 public CONTRACT_VERSION = 1;


    IMintFactory public MINT_FACTORY;
    IFeeHelper public FEE_HELPER;
    
    constructor(address _mintFactory, address _feeHelper) {
        MINT_FACTORY = IMintFactory(_mintFactory);
        FEE_HELPER = IFeeHelper(_feeHelper);
    }
    
    /**
     * @notice Creates a new Token contract and registers it in the TokenFactory.sol.
     */
    
    function createToken (
      TaxToken.ConstructorParams calldata params
      ) public payable returns (address){
        require(msg.value == FEE_HELPER.getGeneratorFee(), 'FEE NOT MET');
        payable(FEE_HELPER.getFeeAddress()).transfer(FEE_HELPER.getGeneratorFee());
        TaxToken newToken = new TaxToken(params, address(MINT_FACTORY));
        MINT_FACTORY.registerToken(msg.sender, address(newToken));
        return address(newToken);
    }
}
// File: Multicall.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Storage.sol";

import "./Ownable.sol";

contract MulticallFacet is Storage, Ownable { 
    struct MulticallAdminUpdateParams {
        TaxSettings _taxSettings;
        TaxSettings _lockSettings;
        CustomTax[] _customTaxes;
        Fees _fees;
        address _transactionTaxWallet;
        uint256 _maxBalanceAfterBuy;
    }

    function multicallAdminUpdate(MulticallAdminUpdateParams calldata params) public onlyOwner {
        // Tax Settings
        if(!isLocked.transactionTax && taxSettings.transactionTax != params._taxSettings.transactionTax) {
            taxSettings.transactionTax = params._taxSettings.transactionTax;
        }
        if(!isLocked.holderTax && taxSettings.holderTax != params._taxSettings.holderTax && !params._taxSettings.canMint) {
            taxSettings.holderTax = params._taxSettings.holderTax;
        }
        if(!isLocked.buyBackTax && taxSettings.buyBackTax != params._taxSettings.buyBackTax) {
            taxSettings.buyBackTax = params._taxSettings.buyBackTax;
        }
        if(!isLocked.lpTax && taxSettings.lpTax != params._taxSettings.lpTax) {
            taxSettings.lpTax = params._taxSettings.lpTax;
        }
        if(!isLocked.canMint && taxSettings.canMint != params._taxSettings.canMint && !taxSettings.holderTax) {
            taxSettings.canMint = params._taxSettings.canMint;
        }
        if(!isLocked.canPause && taxSettings.canPause != params._taxSettings.canPause) {
            taxSettings.canPause = params._taxSettings.canPause;
        }
        if(!isLocked.canBlacklist && taxSettings.canBlacklist != params._taxSettings.canBlacklist) {
            taxSettings.canBlacklist = params._taxSettings.canBlacklist;
        }
        if(!isLocked.maxBalanceAfterBuy && taxSettings.maxBalanceAfterBuy != params._taxSettings.maxBalanceAfterBuy) {
            taxSettings.maxBalanceAfterBuy = params._taxSettings.maxBalanceAfterBuy;
        }
        emit UpdatedSettings(taxSettings);


        // Lock Settings
        if(!isLocked.transactionTax) {
            isLocked.transactionTax = params._lockSettings.transactionTax;
        }
        if(!isLocked.holderTax) {
            isLocked.holderTax = params._lockSettings.holderTax;
        }
        if(!isLocked.buyBackTax) {
            isLocked.buyBackTax = params._lockSettings.buyBackTax;
        }
        if(!isLocked.lpTax) {
            isLocked.lpTax = params._lockSettings.lpTax;
        }
        if(!isLocked.canMint) {
            isLocked.canMint = params._lockSettings.canMint;
        }
        if(!isLocked.canPause) {
            isLocked.canPause = params._lockSettings.canPause;
        }
        if(!isLocked.canBlacklist) {
            isLocked.canBlacklist = params._lockSettings.canBlacklist;
        }
        if(!isLocked.maxBalanceAfterBuy) {
            isLocked.maxBalanceAfterBuy = params._lockSettings.maxBalanceAfterBuy;
        }
        emit UpdatedLockedSettings(isLocked);


        // Custom Taxes
        require(params._customTaxes.length < MaxCustom + 1, "MCT");
        delete customTaxes;

        for(uint i = 0; i < params._customTaxes.length; i++) {
            require(params._customTaxes[i].wallet != address(0), "ZA");
            customTaxes.push(params._customTaxes[i]);
        }
        customTaxLength = params._customTaxes.length;
        emit UpdatedCustomTaxes(params._customTaxes);

        // Fees        
        fees.transactionTax.buy = params._fees.transactionTax.buy;
        fees.transactionTax.sell = params._fees.transactionTax.sell;

        fees.buyBackTax = params._fees.buyBackTax;

        fees.holderTax = params._fees.holderTax;

        fees.lpTax = params._fees.lpTax;

        require(checkMaxTax(true), "BF");
        require(checkMaxTax(false), "SF");
        emit UpdatedTaxFees(params._fees);
        
        // transactionTax address
        require(params._transactionTaxWallet != address(0), "ZA");
        transactionTaxWallet = params._transactionTaxWallet;
        emit UpdatedTransactionTaxAddress(params._transactionTaxWallet);

        // maxBalanceAfterBuy
        if(taxSettings.maxBalanceAfterBuy) {
            maxBalanceAfterBuy = params._maxBalanceAfterBuy;
            emit UpdatedMaxBalanceAfterBuy(params._maxBalanceAfterBuy);
        }
    }

    function checkMaxTax(bool isBuy) internal view returns (bool) {
        uint256 totalTaxes;
        if(isBuy) {
            totalTaxes += fees.transactionTax.buy;
            totalTaxes += fees.holderTax;
            for(uint i = 0; i < customTaxes.length; i++) {
                totalTaxes += customTaxes[i].fee.buy;
            }
        } else {
            totalTaxes += fees.transactionTax.sell;
            totalTaxes += fees.lpTax;
            totalTaxes += fees.holderTax;
            totalTaxes += fees.buyBackTax;
            for(uint i = 0; i < customTaxes.length; i++) {
                totalTaxes += customTaxes[i].fee.sell;
            }
        }
        if(totalTaxes <= MaxTax) {
            return true;
        }
        return false;
    }

    struct AntiBotUpdateParams {
        AntiBotSettings _antiBotSettings;
        SwapWhitelistingSettings _swapWhitelistingSettings;
    }

    // Multicall AntiBot Update
    function multicallAntiBotUpdate(AntiBotUpdateParams calldata params) public onlyOwner {
        // AntiBot
        antiBotSettings.increment = params._antiBotSettings.increment;
        emit UpdatedAntiBotIncrement(antiBotSettings.increment);

        require(params._antiBotSettings.endDate <= 48, "ED");
        antiBotSettings.endDate = params._antiBotSettings.endDate;
        emit UpdatedAntiBotEndDate(antiBotSettings.endDate);

        antiBotSettings.initialMaxHold = params._antiBotSettings.initialMaxHold;
        emit UpdatedAntiBotInitialMaxHold(antiBotSettings.initialMaxHold);

        if(!marketInit) {
            antiBotSettings.isActive = params._antiBotSettings.isActive;
            emit UpdatedAntiBotActiveStatus(antiBotSettings.isActive);
        }

        // SwapWhitelisting
        require(params._swapWhitelistingSettings.endDate <= 48, "ED");
        swapWhitelistingSettings.endDate = params._swapWhitelistingSettings.endDate;
        emit UpdatedSwapWhitelistingEndDate(antiBotSettings.endDate);

        if(!marketInit) {
            swapWhitelistingSettings.isActive = params._swapWhitelistingSettings.isActive;
            emit UpdatedSwapWhitelistingActiveStatus(swapWhitelistingSettings.isActive);
        }
    }
}
// File: Ownable.sol



// SPDX-License-Identifier: MIT

// File @openzeppelin/contracts/access/Ownable.sol@v4.0.0

pragma solidity 0.8.17;

import "./Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// File: Pausable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)

pragma solidity 0.8.17;

import "./Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        require(!paused(), "Pausable: paused");
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        require(paused(), "Pausable: not paused");
        _;
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
// File: Settings.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Storage.sol";
import "./BuyBackWallet.sol";
import "./LPWallet.sol";


import "./IBuyBackWallet.sol";
import "./ILPWallet.sol";
import "./IFeeHelper.sol";
import "./IMintFactory.sol";

import "./Ownable.sol";

contract SettingsFacet is Storage, Ownable {

    function canBlacklistRequire() internal view {
        require(taxSettings.canBlacklist, "NB");
    }

    modifier canBlacklist {
        canBlacklistRequire();
        _;
    }

    function addLPToken(address _newLPToken) public onlyOwner {
        lpTokens[_newLPToken] = true;
        emit AddedLPToken(_newLPToken);
    }

    function removeLPToken(address _lpToken) public onlyOwner {
        lpTokens[_lpToken] = false;
        emit RemovedLPToken(_lpToken);
    }

    function checkMaxTax(bool isBuy) internal view returns (bool) {
        uint256 totalTaxes;
        if(isBuy) {
            totalTaxes += fees.transactionTax.buy;
            totalTaxes += fees.holderTax;
            for(uint i = 0; i < customTaxes.length; i++) {
                totalTaxes += customTaxes[i].fee.buy;
            }
        } else {
            totalTaxes += fees.transactionTax.sell;
            totalTaxes += fees.lpTax;
            totalTaxes += fees.holderTax;
            totalTaxes += fees.buyBackTax;
            for(uint i = 0; i < customTaxes.length; i++) {
                totalTaxes += customTaxes[i].fee.sell;
            }
        }
        if(totalTaxes <= MaxTax) {
            return true;
        }
        return false;
    }

    function paused() public view returns (bool) {
        if(taxSettings.canPause == false) {
            return false;
        }
        return isPaused;
    }

    function togglePause() public onlyOwner returns (bool) {
        require(taxSettings.canPause, "NP");
        isPaused = !isPaused;
        emit ToggledPause(isPaused);
        return isPaused;
    }

    function addBlacklistedAddress(address _address) public onlyOwner canBlacklist {
        IFeeHelper feeHelper = IFeeHelper(IMintFactory(factory).getFeeHelper());
        address feeAddress = feeHelper.getFeeAddress();
        require(_address != feeAddress);
        blacklist[_address] = true;
        emit AddedBlacklistAddress(_address);
    }

    function removeBlacklistedAddress(address _address) public onlyOwner canBlacklist {
        blacklist[_address] = false;
        emit RemovedBlacklistAddress(_address);
    }

    function updateBlacklistBatch(address[] calldata _updatedAddresses, bool _isBlacklisted) public onlyOwner canBlacklist {
        IFeeHelper feeHelper = IFeeHelper(IMintFactory(factory).getFeeHelper());
        address feeAddress = feeHelper.getFeeAddress();
        for(uint i = 0; i < _updatedAddresses.length; i++) {
            if(_updatedAddresses[i] != feeAddress) {
                blacklist[_updatedAddresses[i]] = _isBlacklisted;
                if(_isBlacklisted) {
                    emit AddedBlacklistAddress(_updatedAddresses[i]);
                } else {
                    emit RemovedBlacklistAddress(_updatedAddresses[i]);
                }
            }
        }
    }

    function isBlacklisted(address _address) public view returns (bool) {
        return blacklist[_address];
    }

    function updateCustomTaxes(CustomTax[] calldata _customTaxes) public onlyOwner {
        require(_customTaxes.length < MaxCustom + 1, "MCT");
        delete customTaxes;

        for(uint i = 0; i < _customTaxes.length; i++) {
            require(_customTaxes[i].wallet != address(0));
            customTaxes.push(_customTaxes[i]);
        }
        customTaxLength = _customTaxes.length;

        require(checkMaxTax(true), "BF");
        require(checkMaxTax(false), "SF");
        emit UpdatedCustomTaxes(_customTaxes);
    }

    function updateTaxFees(Fees calldata _updatedFees) public onlyOwner {
        fees.transactionTax.buy = _updatedFees.transactionTax.buy;
        fees.transactionTax.sell = _updatedFees.transactionTax.sell;

        fees.buyBackTax = _updatedFees.buyBackTax;

        fees.holderTax = _updatedFees.holderTax;

        fees.lpTax = _updatedFees.lpTax;

        require(checkMaxTax(true), "BF");
        require(checkMaxTax(false), "SF");
        emit UpdatedTaxFees(_updatedFees);
    }

    function updateTransactionTaxAddress(address _newAddress) public onlyOwner {
        // confirm if this is updateable
        require(_newAddress != address(0));
        transactionTaxWallet = _newAddress;
        emit UpdatedTransactionTaxAddress(_newAddress);
    }

    function lockSettings(TaxSettings calldata _updatedLocks) public onlyOwner {
        if(!isLocked.transactionTax) {
            isLocked.transactionTax = _updatedLocks.transactionTax;
        }
        if(!isLocked.holderTax) {
            isLocked.holderTax = _updatedLocks.holderTax;
        }
        if(!isLocked.buyBackTax) {
            isLocked.buyBackTax = _updatedLocks.buyBackTax;
        }
        if(!isLocked.lpTax) {
            isLocked.lpTax = _updatedLocks.lpTax;
        }
        if(!isLocked.canMint) {
            isLocked.canMint = _updatedLocks.canMint;
        }
        if(!isLocked.canPause) {
            isLocked.canPause = _updatedLocks.canPause;
        }
        if(!isLocked.canBlacklist) {
            isLocked.canBlacklist = _updatedLocks.canBlacklist;
        }
        if(!isLocked.maxBalanceAfterBuy) {
            isLocked.maxBalanceAfterBuy = _updatedLocks.maxBalanceAfterBuy;
        }
        emit UpdatedLockedSettings(isLocked);
    }

    function updateSettings(TaxSettings calldata _updatedSettings) public onlyOwner {
        if(!isLocked.transactionTax && taxSettings.transactionTax != _updatedSettings.transactionTax) {
            taxSettings.transactionTax = _updatedSettings.transactionTax;
        }
        if(!isLocked.holderTax && taxSettings.holderTax != _updatedSettings.holderTax && !_updatedSettings.canMint) {
            taxSettings.holderTax = _updatedSettings.holderTax;
        }
        if(!isLocked.buyBackTax && taxSettings.buyBackTax != _updatedSettings.buyBackTax) {
            taxSettings.buyBackTax = _updatedSettings.buyBackTax;
        }
        if(!isLocked.lpTax && taxSettings.lpTax != _updatedSettings.lpTax) {
            taxSettings.lpTax = _updatedSettings.lpTax;
        }
        if(!isLocked.canMint && taxSettings.canMint != _updatedSettings.canMint && !taxSettings.holderTax) {
            taxSettings.canMint = _updatedSettings.canMint;
        }
        if(!isLocked.canPause && taxSettings.canPause != _updatedSettings.canPause) {
            taxSettings.canPause = _updatedSettings.canPause;
        }
        if(!isLocked.canBlacklist && taxSettings.canBlacklist != _updatedSettings.canBlacklist) {
            taxSettings.canBlacklist = _updatedSettings.canBlacklist;
        }
        if(!isLocked.maxBalanceAfterBuy && taxSettings.maxBalanceAfterBuy != _updatedSettings.maxBalanceAfterBuy) {
            taxSettings.maxBalanceAfterBuy = _updatedSettings.maxBalanceAfterBuy;
        }
        emit UpdatedSettings(taxSettings);
    }

    function updatePairAddress(address _newPairAddress) public onlyOwner {
        pairAddress = _newPairAddress;
        emit UpdatedPairAddress(_newPairAddress);
    }

    function updateTaxHelperIndex(uint8 _newIndex) public onlyOwner {
        taxHelperIndex = _newIndex;
        emit UpdatedTaxHelperIndex(_newIndex);
    }
}
// File: Storage.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./ILosslessController.sol";

contract Storage {

    uint256 public CONTRACT_VERSION = 1;

    struct TaxSettings {
        bool transactionTax;
        bool buyBackTax;
        bool holderTax;
        bool lpTax;
        bool canBlacklist;
        bool canMint;
        bool canPause;
        bool maxBalanceAfterBuy;
    }

    struct Fee {
        uint256 buy;
        uint256 sell;
    }

    struct Fees {
        Fee transactionTax;
        uint256 buyBackTax;
        uint256 holderTax;
        uint256 lpTax;
    }

    struct CustomTax {
        string name;
        Fee fee;
        address wallet;
    }

    TaxSettings public taxSettings;
    TaxSettings public isLocked;
    Fees public fees;
    CustomTax[] public customTaxes;

    address public transactionTaxWallet;
    uint256 public customTaxLength = 0;
    uint256 public MaxTax = 3000;
    uint8 public MaxCustom = 10;

    uint256 internal DENOMINATOR;

    mapping (address => uint256) internal _rOwned;
    mapping (address => uint256) internal _tOwned;
    mapping (address => mapping (address => uint256)) public _allowances;

    mapping (address => bool) public _isExcluded;
    address[] internal _excluded;
   
    uint256 constant MAX = ~uint256(0);
    uint256 internal _tTotal;
    uint256 internal _rTotal;
    uint256 public _tFeeTotal;

    mapping (address => bool) public lpTokens;
    
    string internal _name;
    string internal _symbol;
    uint8 internal _decimals;
    address internal _creator;

    address public factory;

    address public buyBackWallet;
    address public lpWallet;

    bool internal isPaused = false;

    bool internal isTaxed = false;
    
    mapping(address => bool) internal blacklist;
    mapping(address => bool) internal swapWhitelist;
    mapping(address => bool) internal maxBalanceWhitelist;

    address public pairAddress;

    uint256 public taxHelperIndex;

    // AntiBot Variables

    bool public marketInit = false;
    uint256 public marketInitBlockTime;

    struct AntiBotSettings {
        uint256 startBlock;
        uint256 endDate;
        uint256 increment;
        uint256 initialMaxHold;
        bool isActive;
    }

    AntiBotSettings public antiBotSettings;

    mapping (address => uint256) internal antiBotBalanceTracker;

    uint256 public maxBalanceAfterBuy;

    struct SwapWhitelistingSettings {
        uint256 endDate;
        bool isActive;
    }
    
    SwapWhitelistingSettings public swapWhitelistingSettings;

    // Lossless data and events

    address public recoveryAdmin;
    address internal recoveryAdminCandidate;
    bytes32 internal recoveryAdminKeyHash;
    address public admin;
    uint256 public timelockPeriod;
    uint256 public losslessTurnOffTimestamp;
    bool public isLosslessTurnOffProposed;
    bool public isLosslessOn;
    ILosslessController public lossless;

    event AdminChanged(address indexed previousAdmin, address indexed newAdmin);
    event RecoveryAdminChangeProposed(address indexed candidate);
    event RecoveryAdminChanged(address indexed previousAdmin, address indexed newAdmin);
    event LosslessTurnOffProposed(uint256 turnOffDate);
    event LosslessTurnedOff();
    event LosslessTurnedOn();

    // Events 

    event TokenCreated(string name, string symbol, uint8 decimals, uint256 totalSupply, uint256 reflectionTotalSupply);

    event AddedBlacklistAddress(address _address);
    event RemovedBlacklistAddress(address _address);
    event ToggledPause(bool _isPaused);
    event AddedLPToken(address _newLPToken);
    event RemovedLPToken(address _lpToken);
    event CreatedBuyBackWallet(address _wallet);
    event CreatedLPWallet(address _wallet);
    event UpdatedBuyBackWalletThreshold(uint256 _newThreshold);
    event UpdatedLPWalletThreshold(uint256 _newThreshold);

    event MarketInit(uint256 timestamp, uint256 blockNumber);

    event BuyBackTaxInitiated(address _sender, uint256 _fee, address _wallet, bool _isBuy);
    event TransactionTaxInitiated(address _sender, uint256 _fee, address _wallet, bool _isBuy);
    event LPTaxInitiated(address _sender, uint256 _fee, address _wallet, bool _isBuy);
    event CustomTaxInitiated(address _sender, uint256 _fee, address _wallet, bool _isBuy);

    event UpdatedCustomTaxes(CustomTax[] _customTaxes);
    event UpdatedTaxFees(Fees _updatedFees);
    event UpdatedTransactionTaxAddress(address _newAddress);
    event UpdatedLockedSettings(TaxSettings _updatedLocks);
    event UpdatedSettings(TaxSettings _updatedSettings);
    event UpdatedPairAddress(address _newPairAddress);
    event UpdatedTaxHelperIndex(uint _newIndex);

    event Reflect(uint256 tAmount, uint256 rAmount, uint256 rTotal_, uint256 teeTotal_);
    event ExcludedAccount(address account);
    event IncludedAccount(address account);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    // AntiBot

    event UpdatedAntiBotSettings(AntiBotSettings _antiBotSettings);
    event UpdatedSwapWhitelistingSettings(SwapWhitelistingSettings _swapWhitelistingSettings);

    event UpdatedAntiBotIncrement(uint256 _updatedIncrement);
    event UpdatedAntiBotEndDate(uint256 _updatedEndDate);
    event UpdatedAntiBotInitialMaxHold(uint256 _updatedInitialMaxHold);
    event UpdatedAntiBotActiveStatus(bool _isActive);
    event UpdatedSwapWhitelistingEndDate(uint256 _updatedEndDate);
    event UpdatedSwapWhitelistingActiveStatus(bool _isActive);
    event UpdatedMaxBalanceAfterBuy(uint256 _newMaxBalance);

    event AddedMaxBalanceWhitelistAddress(address _address);   
    event RemovedMaxBalanceWhitelistAddress(address _address);        
    event AddedSwapWhitelistAddress(address _address);
    event RemovedSwapWhitelistAddress(address _address);
}
// File: Tax.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

// This contract logs all tokens on the platform

pragma solidity 0.8.17;

import "./IERC20.sol";
import "./ITaxHelper.sol";
import "./IMintFactory.sol";
import "./ILosslessController.sol";
import "./IFeeHelper.sol";

import "./Ownable.sol";
import "./FullMath.sol";

import "./Storage.sol";

import "./BuyBackWallet.sol";
import "./LPWallet.sol";


contract TaxFacet is Storage, Ownable{

    function paused() internal view returns (bool) {
        return isPaused;
    }

    function isBlacklisted(address _address) internal view returns (bool) {
        return blacklist[_address];
    }

    /// @notice Handles the taxes for the token.
    /// Calls the appropriate tax helper contract to handle 
    /// LP and BuyBack tax logic
    /// @dev handles every tax within the tax facet. 
    /// @param sender the one sending the transaction
    /// @param recipient the one receiving the transaction
    /// @param amount the amount of tokens being sent
    /// @return totalTaxAmount the total amount of the token taxed
    function handleTaxes(address sender, address recipient, uint256 amount) public virtual returns (uint256 totalTaxAmount) {
        // restrict it to being only called by registered tokens
        require(IMintFactory(factory).tokenIsRegistered(address(this)));
        bool isBuy = false;

        if(lpTokens[sender]) {
            isBuy = true;
            if(!marketInit) {
                marketInit = true;
                antiBotSettings.startBlock = block.number;
                marketInitBlockTime = block.timestamp;
                emit MarketInit(block.timestamp, block.number);
            }
        }

        if(!lpTokens[sender] && !lpTokens[recipient]) {
            return 0;
        }

        ITaxHelper TaxHelper = ITaxHelper(IMintFactory(factory).getTaxHelperAddress(taxHelperIndex));
        if(sender == address(TaxHelper) || recipient == address(TaxHelper)) {
            return 0;
        }
        totalTaxAmount;
        uint256 fee;
        if(taxSettings.buyBackTax && !isBuy) {
            if(TaxHelper.lpTokenHasReserves(pairAddress)) {
                fee = amount * fees.buyBackTax / DENOMINATOR;
            }
            
            if(fee != 0) {
                _transfer(sender, address(TaxHelper), fee);

                TaxHelper.initiateBuyBackTax(address(this), address(buyBackWallet));
                emit BuyBackTaxInitiated(sender, fee, address(buyBackWallet), isBuy);
                totalTaxAmount += fee;
            }
            fee = 0;
        }
        if(taxSettings.transactionTax) {
            if(isBuy) {
                fee = amount * fees.transactionTax.buy / DENOMINATOR;
            } else {
                fee = amount * fees.transactionTax.sell / DENOMINATOR;
            }
            if(fee != 0) {
                _transfer(sender, transactionTaxWallet, fee);
                emit TransactionTaxInitiated(sender, fee, transactionTaxWallet, isBuy);
                totalTaxAmount += fee;
            }
            fee = 0;
        }
        if(taxSettings.lpTax && !isBuy) {
            if(TaxHelper.lpTokenHasReserves(pairAddress)) {
                fee = amount * fees.lpTax / DENOMINATOR;
            }
            if(fee != 0) {
                _transfer(sender, address(TaxHelper), fee);
                TaxHelper.initiateLPTokenTax(address(this), address(lpWallet));
                emit LPTaxInitiated(sender, fee, address(lpWallet), isBuy);
                totalTaxAmount += fee;
            }
            fee = 0;
        }
        if(customTaxes.length > 0) {
            for(uint8 i = 0; i < customTaxes.length; i++) {
                uint256 customFee;
                if(isBuy) {
                    customFee = amount * customTaxes[i].fee.buy / DENOMINATOR;
                } else {
                    customFee = amount * customTaxes[i].fee.sell / DENOMINATOR;
                }
                fee += customFee;
                if(fee != 0) {
                    totalTaxAmount += fee;
                    _transfer(sender, customTaxes[i].wallet, fee);
                    emit CustomTaxInitiated(sender, fee, customTaxes[i].wallet, isBuy);
                    fee = 0;
                }
            }
        }    
    }

    /// @notice internal transfer method
    /// @dev includes checks for all features not handled by handleTaxes()
    /// @param sender the one sending the transaction
    /// @param recipient the one receiving the transaction
    /// @param amount the amount of tokens being sent
    function _transfer(address sender, address recipient, uint256 amount) public {
        // restrict it to being only called by registered tokens
        if(!IMintFactory(factory).tokenGeneratorIsAllowed(msg.sender)) {
            require(IMintFactory(factory).tokenIsRegistered(address(this)));
        }
        require(sender != address(0), "ETFZ");
        require(recipient != address(0), "ETTZ");
        require(amount > 0, "TGZ");
        require(!paused(), "TP");
        require(!isBlacklisted(msg.sender), "SB");
        require(!isBlacklisted(recipient), "RB"); 
        require(!isBlacklisted(tx.origin), "SB");
        // Reflection Transfers
        if(taxSettings.holderTax) {
            if (_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferFromExcluded(sender, recipient, amount);
            } else if (!_isExcluded[sender] && _isExcluded[recipient]) {
                _transferToExcluded(sender, recipient, amount);
            } else if (!_isExcluded[sender] && !_isExcluded[recipient]) {
                _transferStandard(sender, recipient, amount);
            } else if (_isExcluded[sender] && _isExcluded[recipient]) {
                _transferBothExcluded(sender, recipient, amount);
            } else {
                _transferStandard(sender, recipient, amount);
            }
        } else {
            // Non Reflection Transfer
            _beforeTokenTransfer(sender, recipient, amount);

            uint256 senderBalance = _tOwned[sender];
            require(senderBalance >= amount, "ETA");
            _tOwned[sender] = senderBalance - amount;
            _tOwned[recipient] += amount;

            emit Transfer(sender, recipient, amount);
        }
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }


    // Reflection Functions


    function reflect(uint256 tAmount) public {
        address sender = _msgSender();
        require(!_isExcluded[sender], "EA");
        (uint256 rAmount,,,,) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender] - rAmount;
        _rTotal = _rTotal - rAmount;
        _tFeeTotal = _tFeeTotal + tAmount;
        emit Reflect(tAmount, rAmount, _rTotal, _tFeeTotal);
    }

    function reflectionFromToken(uint256 tAmount, bool deductTransferFee) public view returns(uint256) {
        require(tAmount <= _tTotal, "ALS");
        if (!deductTransferFee) {
            (uint256 rAmount,,,,) = _getValues(tAmount);
            return rAmount;
        } else {
            (,uint256 rTransferAmount,,,) = _getValues(tAmount);
            return rTransferAmount;
        }
    }

    function tokenFromReflection(uint256 rAmount) public view returns(uint256)  {
        require(rAmount <= _rTotal, "ALR");
        uint256 currentRate = _getRate();
        return rAmount / currentRate;
    }

    function excludeAccount(address account) external onlyOwner {
        require(!_isExcluded[account], "AE");
        if(_rOwned[account] > 0) {
            _tOwned[account] = tokenFromReflection(_rOwned[account]);
        }
        _isExcluded[account] = true;
        _excluded.push(account);
        emit ExcludedAccount(account);
    }

    function includeAccount(address account) external onlyOwner {
        require(_isExcluded[account], "AI");
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_excluded[i] == account) {
                _excluded[i] = _excluded[_excluded.length - 1];
                _tOwned[account] = 0;
                _isExcluded[account] = false;
                _excluded.pop();
                break;
            }
        }
        emit IncludedAccount(account);
    }

    function isExcluded(address account) external view returns(bool) {
        return _isExcluded[account];
    }

    function _transferStandard(address sender, address recipient, uint256 tAmount) private {
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender] - rAmount;
        _rOwned[recipient] = _rOwned[recipient] + rTransferAmount;    
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _transferToExcluded(address sender, address recipient, uint256 tAmount) private {
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender] - rAmount;
        _tOwned[recipient] = _tOwned[recipient] + tTransferAmount;
        _rOwned[recipient] = _rOwned[recipient] + rTransferAmount;           
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _transferFromExcluded(address sender, address recipient, uint256 tAmount) private {
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee) = _getValues(tAmount);
        _tOwned[sender] = _tOwned[sender] - tAmount;
        _rOwned[sender] = _rOwned[sender] - rAmount;
        _rOwned[recipient] = _rOwned[recipient] + rTransferAmount;   
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _transferBothExcluded(address sender, address recipient, uint256 tAmount) private {
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee) = _getValues(tAmount);
        _tOwned[sender] = _tOwned[sender] - tAmount;
        _rOwned[sender] = _rOwned[sender] - rAmount;
        _tOwned[recipient] = _tOwned[recipient] + tTransferAmount;
        _rOwned[recipient] = _rOwned[recipient] + rTransferAmount;        
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _reflectFee(uint256 rFee, uint256 tFee) private {
        _rTotal = _rTotal - rFee;
        _tFeeTotal = _tFeeTotal + tFee;
    }

    function _getValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256, uint256) {
        (uint256 tTransferAmount, uint256 tFee) = _getTValues(tAmount);
        uint256 currentRate = _getRate();
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, currentRate);
        return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee);
    }

    function _getTValues(uint256 tAmount) private view returns (uint256, uint256) {
        uint256 tFee = tAmount / fees.holderTax;
        uint256 tTransferAmount = tAmount - tFee;
        return (tTransferAmount, tFee);
    }

    function _getRValues(uint256 tAmount, uint256 tFee, uint256 currentRate) private pure returns (uint256, uint256, uint256) {
        uint256 rAmount = tAmount * currentRate;
        uint256 rFee = tFee * currentRate;
        uint256 rTransferAmount = rAmount - rFee;
        return (rAmount, rTransferAmount, rFee);
    }

    function _getRate() private view returns(uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply / tSupply;
    }

    function _getCurrentSupply() private view returns(uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;      
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply) return (_rTotal, _tTotal);
            rSupply = rSupply - _rOwned[_excluded[i]];
            tSupply = tSupply - _tOwned[_excluded[i]];
        }
        if (rSupply < _rTotal / _tTotal) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }

    function burn(uint256 amount) public {
        if (isLosslessOn) {
            lossless.beforeBurn(_msgSender(), amount);
        } 
        address taxHelper = IMintFactory(factory).getTaxHelperAddress(taxHelperIndex);
        require(msg.sender == taxHelper || msg.sender == owner(), "RA");
        _burn(owner(), amount);
    }

    /// @notice custom burn to handle reflection
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "EBZ");

        if (isLosslessOn) {
            lossless.beforeBurn(account, amount);
        } 

        _beforeTokenTransfer(account, address(0), amount);

        if(taxSettings.holderTax && !_isExcluded[account]) {
            (uint256 rAmount,,,,) = _getValues(amount);
            _rOwned[account] = _rOwned[account] - rAmount;
            _rTotal = _rTotal - rAmount;
            _tFeeTotal = _tFeeTotal + amount;
        }

        uint256 accountBalance = _tOwned[account];
        require(accountBalance >= amount, "EBB");
        _tOwned[account] = accountBalance - amount;
        _tTotal -= amount;

        emit Transfer(account, address(0), amount);
    }
    
}
// File: TaxHelperUniswapV2.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.


pragma solidity 0.8.17;

import "./Ownable.sol";

import "./IUniswapV2Router02.sol";
import "./IUniswapV2Factory.sol";
import "./IUniswapV2Pair.sol";
import "./IBuyBackWallet.sol";
import "./ILPWallet.sol";
import "./ITaxToken.sol";
import "./IMintFactory.sol";

// add events

contract TaxHelperUniswapV2 is Ownable{
    
    IUniswapV2Router02 router;
    IUniswapV2Factory factory;
    IMintFactory mintFactory;

    // event Buy
    event CreatedLPToken(address token0, address token1, address LPToken);

    constructor(address swapV2Router, address swapV2Factory, address _mintFactory) {
    router = IUniswapV2Router02(swapV2Router);
    factory = IUniswapV2Factory(swapV2Factory);
    mintFactory = IMintFactory(_mintFactory);
 
    }

    modifier isToken() {
        require(mintFactory.tokenIsRegistered(msg.sender), "RA");
        _;
    }

    function initiateBuyBackTax(address _token, address _wallet) payable external isToken returns(bool) {
        ITaxToken token = ITaxToken(_token);
        uint256 _amount = token.balanceOf(address(this));
        address[] memory addressPaths = new address[](2);
        addressPaths[0] = _token;
        addressPaths[1] = router.WETH();
        token.approve(address(router), _amount);
        if(_amount > 0) {
            router.swapExactTokensForETHSupportingFeeOnTransferTokens(_amount, 0, addressPaths, _wallet, block.timestamp);
        }
        IBuyBackWallet buyBackWallet = IBuyBackWallet(_wallet);
        bool res = buyBackWallet.checkBuyBackTrigger();
        if(res) {
            addressPaths[0] = router.WETH();
            addressPaths[1] = _token;
            uint256 amountEth = buyBackWallet.sendEthToTaxHelper();
            uint256 balanceBefore = token.balanceOf(address(this));
            router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amountEth}(0, addressPaths, address(this), block.timestamp);
            // burn baby burn!
            uint256 balanceAfter = token.balanceOf(address(this));
            uint256 amountToBurn = balanceAfter - balanceBefore;
            token.approve(token.owner(), amountToBurn);
            token.buyBackBurn(amountToBurn);
        }
        return true;
    }

    function initiateLPTokenTax(address _token, address _wallet) external isToken returns (bool) {
        ITaxToken token = ITaxToken(_token);
        uint256 _amount = token.balanceOf(address(this));
        address[] memory addressPaths = new address[](2);
        addressPaths[0] = _token;
        addressPaths[1] = router.WETH();
        uint256 halfAmount = _amount / 2;
        uint256 otherHalf = _amount - halfAmount;
        token.transfer(_wallet, otherHalf);
        token.approve(address(router), halfAmount);
        if(halfAmount > 0) {
            router.swapExactTokensForETHSupportingFeeOnTransferTokens(halfAmount, 0, addressPaths, _wallet, block.timestamp);
        }
        ILPWallet lpWallet = ILPWallet(_wallet);
        bool res = lpWallet.checkLPTrigger();
        if(res) {
            lpWallet.transferBalanceToTaxHelper();
            uint256 amountEth = lpWallet.sendEthToTaxHelper();
            uint256 tokenBalance = token.balanceOf(address(this));
            token.approve(address(router), tokenBalance);
            router.addLiquidityETH{value: amountEth}(_token, tokenBalance, 0, 0, token.owner(), block.timestamp + 20 minutes);
            uint256 ethDust = address(this).balance;
            if(ethDust > 0) {
                (bool sent,) = _wallet.call{value: ethDust}("");
                require(sent, "Failed to send Ether");
            }
            uint256 tokenDust = token.balanceOf(address(this));
            if(tokenDust > 0) {
                token.transfer(_wallet, tokenDust);
            }
        }
        return true;
    }    
    
    function createLPToken() external returns(address lpToken) {
        lpToken = factory.createPair(msg.sender, router.WETH());
        emit CreatedLPToken(msg.sender, router.WETH(), lpToken);
    }

    function lpTokenHasReserves(address _lpToken) public view returns (bool) {
        (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(_lpToken).getReserves();
        return reserve0 > 0 && reserve1 > 0;
    }

    receive() payable external {
    }

} 
// File: TaxToken.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.


pragma solidity 0.8.17;

import "./IERC20.sol";
import "./ITaxHelper.sol";
import "./IMintFactory.sol";
import "./IBuyBackWallet.sol";
import "./ILPWallet.sol";
import "./ISettings.sol";
import "./IFacetHelper.sol";
import "./IFeeHelper.sol";

import "./Ownable.sol";
import "./FullMath.sol";

import "./BuyBackWallet.sol";
import "./LPWallet.sol";

import "./Storage.sol";

contract TaxToken is Storage, Ownable{
    

    struct ConstructorParams {
        string name_; 
        string symbol_; 
        uint8 decimals_; 
        address creator_;
        uint256 tTotal_;
        uint256 _maxTax;
        TaxSettings _settings;
        TaxSettings _lockedSettings;
        Fees _fees;
        address _transactionTaxWallet;
        CustomTax[] _customTaxes;
        uint256 lpWalletThreshold;
        uint256 buyBackWalletThreshold;
        uint256 _taxHelperIndex;
        address admin_;
        address recoveryAdmin_;
        bool isLossless_;
        AntiBotSettings _antiBotSettings;
        uint256 _maxBalanceAfterBuy;
        SwapWhitelistingSettings _swapWhitelistingSettings;
    }

    constructor(
        ConstructorParams memory params,
        address _factory
        ) {
        address constructorFacetAddress = IFacetHelper(IMintFactory(_factory).getFacetHelper()).getConstructorFacet();
        (bool success, bytes memory result) = constructorFacetAddress.delegatecall(abi.encodeWithSignature("constructorHandler((string,string,uint8,address,uint256,uint256,(bool,bool,bool,bool,bool,bool,bool,bool),(bool,bool,bool,bool,bool,bool,bool,bool),((uint256,uint256),uint256,uint256,uint256),address,(string,(uint256,uint256),address)[],uint256,uint256,uint256,address,address,bool,(uint256,uint256,uint256,uint256,bool),uint256,(uint256,bool)),address)", params, _factory));
        if (!success) {
            if (result.length < 68) revert();
            revert(abi.decode(result, (string)));
        }
        IFeeHelper feeHelper = IFeeHelper(IMintFactory(factory).getFeeHelper());
        uint256 fee = FullMath.mulDiv(params.tTotal_, feeHelper.getFee(), feeHelper.getFeeDenominator());
        address feeAddress = feeHelper.getFeeAddress();
        _approve(params.creator_, msg.sender, fee);
        isTaxed = true;
        transferFrom(params.creator_, feeAddress, fee);
    }

    /// @notice this is the power behind Lossless
    function transferOutBlacklistedFunds(address[] calldata from) external {
        require(isLosslessOn); // added by us for extra protection
        require(_msgSender() == address(lossless), "LOL");
        for (uint i = 0; i < from.length; i++) {
            _transfer(from[i], address(lossless), balanceOf(from[i]));
        }
    }

    /// @notice Checks whether an address is blacklisted
    /// @param _address the address to check
    /// @return bool is blacklisted or not
    function isBlacklisted(address _address) public view returns (bool) {
        return blacklist[_address];
    }

    /// @notice Checks whether the contract has paused transactions
    /// @return bool is paused or not
    function paused() public view returns (bool) {
        if(taxSettings.canPause == false) {
            return false;
        }
        return isPaused;
    }

    /// @notice Handles the burning of token during the buyback tax process
    /// @dev must first receive the amount to be burned from the taxHelper contract (see initial transfer in function)
    /// @param _amount the amount to burn
    function buyBackBurn(uint256 _amount) external {
        address taxHelper = IMintFactory(factory).getTaxHelperAddress(taxHelperIndex);
        require(msg.sender == taxHelper, "RA");
        _transfer(taxHelper, owner(), _amount);

        _beforeTokenTransfer(owner(), address(0), _amount);

        address taxFacetAddress = IFacetHelper(IMintFactory(factory).getFacetHelper()).getTaxFacet();
        (bool success, bytes memory result) = taxFacetAddress.delegatecall(abi.encodeWithSignature("burn(uint256)", _amount));
        if (!success) {
            if (result.length < 68) revert();
            revert(abi.decode(result, (string)));
        }
    }

    /// @notice Handles the taxes for the token.
    /// @dev handles every tax within the tax facet. 
    /// @param sender the one sending the transaction
    /// @param recipient the one receiving the transaction
    /// @param amount the amount of tokens being sent
    /// @return totalTaxAmount the total amount of the token taxed
    function handleTaxes(address sender, address recipient, uint256 amount) internal virtual returns (uint256 totalTaxAmount) {
        address taxFacetAddress = IFacetHelper(IMintFactory(factory).getFacetHelper()).getTaxFacet();
        (bool success, bytes memory result) = taxFacetAddress.delegatecall(abi.encodeWithSignature("handleTaxes(address,address,uint256)", sender, recipient, amount));
        if (!success) {
            if (result.length < 68) revert();
            revert(abi.decode(result, (string)));
        }
        return abi.decode(result, (uint256));

    }

    // ERC20 Functions

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view returns (uint256) {
        return _tTotal;
    }

    /// @dev modified to handle if the token has reflection active in it settings
    function balanceOf(address account) public view returns (uint256) {
        if(taxSettings.holderTax) {
            if (_isExcluded[account]) return _tOwned[account];
            return tokenFromReflection(_rOwned[account]); 
        }
        return _tOwned[account];
    }

    // Reflection Functions 
    // necessary to get reflection balance

    function tokenFromReflection(uint256 rAmount) public view returns(uint256) {
        require(rAmount <= _rTotal, "ALR");
        uint256 currentRate =  _getRate();
        return rAmount / currentRate;
    }

    function _getRate() public view returns(uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply / tSupply;
    }

    function _getCurrentSupply() public view returns(uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;      
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply) return (_rTotal, _tTotal);
            rSupply = rSupply - _rOwned[_excluded[i]];
            tSupply = tSupply - _tOwned[_excluded[i]];
        }
        if (rSupply < _rTotal / _tTotal) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }


    // ERC20 Functions continued 
    /// @dev modified slightly to add taxes

    function transfer(address recipient, uint256 amount) public returns (bool) {
        if(!isTaxed) {
            isTaxed = true;
            uint256 totalTaxAmount = handleTaxes(_msgSender(), recipient, amount);
            amount -= totalTaxAmount;
        }
        if (isLosslessOn) {
            lossless.beforeTransfer(_msgSender(), recipient, amount);
        } 
        _transfer(_msgSender(), recipient, amount);
        isTaxed = false;
        if (isLosslessOn) {
            lossless.afterTransfer(_msgSender(), recipient, amount);
        } 
        return true;
    }

    function allowance(address _owner, address spender) public view returns (uint256) {
        return _allowances[_owner][spender];
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        if (isLosslessOn) {
            lossless.beforeApprove(_msgSender(), spender, amount);
        }
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        if(!isTaxed) {
            isTaxed = true;
            uint256 totalTaxAmount = handleTaxes(sender, recipient, amount);
            amount -= totalTaxAmount;
        }
        if (isLosslessOn) {
            lossless.beforeTransferFrom(_msgSender(), sender, recipient, amount);
        }
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ETA");

        _approve(sender, _msgSender(), _allowances[sender][_msgSender()] - amount);

        isTaxed = false;
        if (isLosslessOn) {
            lossless.afterTransfer(_msgSender(), recipient, amount);
        } 
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        if (isLosslessOn) {
            lossless.beforeIncreaseAllowance(_msgSender(), spender, addedValue);
        }
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
         if (isLosslessOn) {
            lossless.beforeDecreaseAllowance(_msgSender(), spender, subtractedValue);
        }
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "EABZ");
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        return true;
    }

    function _approve(address _owner, address spender, uint256 amount) private {
        require(_owner != address(0), "EAFZ");
        require(spender != address(0), "EATZ");
        if (isLosslessOn) {
            lossless.beforeApprove(_owner, spender, amount);
        } 

        _allowances[_owner][spender] = amount;
        emit Approval(_owner, spender, amount);
    }

    function _transfer(address sender, address recipient, uint256 amount) private {
        // AntiBot Checks
        address antiBotFacetAddress = IFacetHelper(IMintFactory(factory).getFacetHelper()).getAntiBotFacet();
        if(marketInit && antiBotSettings.isActive && lpTokens[sender]) {
            (bool success, bytes memory result) = antiBotFacetAddress.delegatecall(abi.encodeWithSignature("antiBotCheck(uint256,address)", amount, recipient));
            if (!success) {
                if (result.length < 68) revert();
                revert(abi.decode(result, (string)));
            }
        } 
        if(taxSettings.maxBalanceAfterBuy && lpTokens[sender]) {
            (bool success2, bytes memory result2) = antiBotFacetAddress.delegatecall(abi.encodeWithSignature("maxBalanceAfterBuyCheck(uint256,address)", amount, recipient));
            if (!success2) {
                if (result2.length < 68) revert();
                revert(abi.decode(result2, (string)));
            }
        } 
        if(marketInit && swapWhitelistingSettings.isActive && lpTokens[sender]) {
            (bool success3, bytes memory result3) = antiBotFacetAddress.delegatecall(abi.encodeWithSignature("swapWhitelistingCheck(address)", recipient));
            if (!success3) {
                if (result3.length < 68) revert();
                revert(abi.decode(result3, (string)));
            }
        } 
        address taxFacetAddress = IFacetHelper(IMintFactory(factory).getFacetHelper()).getTaxFacet();
        (bool success4, bytes memory result4) = taxFacetAddress.delegatecall(abi.encodeWithSignature("_transfer(address,address,uint256)", sender, recipient, amount));
        if (!success4) {
            if (result4.length < 68) revert();
            revert(abi.decode(result4, (string)));
        }
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }

    function mint(uint256 amount) public onlyOwner {
        if (isLosslessOn) {
            lossless.beforeMint(_msgSender(), amount);
        } 
        _mint(msg.sender, amount);
    }

    /// @notice custom mint to handle fees
    function _mint(address account, uint256 amount) internal virtual onlyOwner {
        require(account != address(0), "EMZ");
        require(taxSettings.canMint, "NM");
        require(!taxSettings.holderTax, "NM");
        if (isLosslessOn) {
            lossless.beforeMint(account, amount);
        } 

        IFeeHelper feeHelper = IFeeHelper(IMintFactory(factory).getFeeHelper());
        uint256 fee = FullMath.mulDiv(amount, feeHelper.getFee(), feeHelper.getFeeDenominator());
        address feeAddress = feeHelper.getFeeAddress();

        _beforeTokenTransfer(address(0), account, amount);
        _tTotal += amount;
        _tOwned[feeAddress] += fee;
        _tOwned[account] += amount - fee;

        emit Transfer(address(0), feeAddress, fee);
        emit Transfer(address(0), account, amount);
    }

    function burn(uint256 amount) public {
        address taxFacetAddress = IFacetHelper(IMintFactory(factory).getFacetHelper()).getTaxFacet();
        (bool success, bytes memory result) = taxFacetAddress.delegatecall(abi.encodeWithSignature("burn(uint256)", amount));
        if (!success) {
            if (result.length < 68) revert();
            revert(abi.decode(result, (string)));
        }
    }

    /// @notice Handles all facet logic
    /// @dev Implements a customized version of the EIP-2535 Diamond Standard to add extra functionality to the contract
    /// https://github.com/mudgen/diamond-3 
    fallback() external {
        address facetHelper = IMintFactory(factory).getFacetHelper(); 
        address facet = IFacetHelper(facetHelper).getFacetAddressFromSelector(msg.sig);
        require(facet != address(0), "Function does not exist");
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
    
            let result := delegatecall(
                gas(),
                facet,
                ptr,
                calldatasize(),
                0,
                0
            )

            let size := returndatasize()
            returndatacopy(ptr, 0, size)

            switch result
            case 0 {
                revert(ptr, size)
            }
            default {
                return(ptr, size)
            }
        }
    }
  
}
// File: TokenFees.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity ^0.8.0;

import "./Ownable.sol";

interface ITokenFees {
    function getTokenFee() view external returns(uint256);
    
    function setTokenFee(uint _tokenFee) external;
    
    function getTokenFeeAddress() view external returns(address);
    
    function setTokenFeeAddress(address payable _tokenFeeAddress) external;
}

contract TokenFees is Ownable{
    
    struct Settings {
        uint256 TOKEN_FEE; 
        uint256 DENOMINATOR;
        address payable TOKEN_FEE_ADDRESS;
    }
    
    Settings public SETTINGS;
    
    constructor() {
        SETTINGS.TOKEN_FEE = 1;
        SETTINGS.DENOMINATOR = 10000;
        SETTINGS.TOKEN_FEE_ADDRESS = payable(msg.sender);
    }
    
    function getTokenFee() view external returns(uint256) {
        return SETTINGS.TOKEN_FEE;
    }
    
    function setTokenFee(uint _tokenFee) external onlyOwner {
        SETTINGS.TOKEN_FEE = _tokenFee;
    }
    
    function getTokenFeeAddress() view external returns(address) {
        return SETTINGS.TOKEN_FEE_ADDRESS;
    }
    
    function setTokenFeeAddress(address payable _tokenFeeAddress) external onlyOwner {
        SETTINGS.TOKEN_FEE_ADDRESS = _tokenFeeAddress;
    }
} 
// File: Wallets.sol



// SPDX-License-Identifier: UNLICENSED
// ALL RIGHTS RESERVED

// Unicrypt by SDDTech reserves all rights on this code. You may NOT copy these contracts.

pragma solidity 0.8.17;

import "./Storage.sol";
import "./BuyBackWallet.sol";
import "./LPWallet.sol";


import "./IBuyBackWallet.sol";
import "./ILPWallet.sol";

import "./Ownable.sol";

contract WalletsFacet is Storage, Ownable {


    function createBuyBackWallet(address _factory, address _token, uint256 _newThreshold) external returns (address) {
        BuyBackWallet newBuyBackWallet = new BuyBackWallet(_factory, _token,_newThreshold);
        emit CreatedBuyBackWallet(address(newBuyBackWallet));
        return address(newBuyBackWallet);
    }

    function createLPWallet(address _factory, address _token, uint256 _newThreshold) external returns (address) {
        LPWallet newLPWallet = new LPWallet(_factory, _token, _newThreshold);
        emit CreatedLPWallet(address(newLPWallet));
        return address(newLPWallet);
    }

    function updateBuyBackWalletThreshold(uint256 _newThreshold) public onlyOwner {
        IBuyBackWallet(buyBackWallet).updateThreshold(_newThreshold);
        emit UpdatedBuyBackWalletThreshold(_newThreshold);
    }

    function updateLPWalletThreshold(uint256 _newThreshold) public onlyOwner {
        ILPWallet(lpWallet).updateThreshold(_newThreshold);
        emit UpdatedLPWalletThreshold(_newThreshold);
    }

}