// File: contracts/FXISportsToken.sol



//SPDX-License-Identifier: None

pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "./interfaces/IUniswapV2Factory.sol";
import "./interfaces/IUniswapV2Router02.sol";
import "./interfaces/IFXISportsToken.sol";

/// @title FXI Sports Token
/// @title https://fx1.io/
/// @title https://t.me/fx1_sports_portal
/// @author https://PROOFplatform.io
/// @author https://5thWeb.io

contract FXISportsToken is Ownable, IFXISportsToken {
    /// @notice Maps an address to its token balance
    mapping(address => uint256) private _balances;
    /// @notice Maps addresses to allowances granted by token holders
    mapping(address => mapping(address => uint256)) private _allowances;
    /// @notice Maps addresses to indicate whether they are excluded from transaction fees
    mapping(address => bool) public excludedFromFees;
    /// @notice Maps addresses to indicate whether they are excluded from maximum wallet balance limits
    mapping(address => bool) public excludedFromMaxWallet;
    /// @notice Maps addresses to indicate whether they are whitelisted
    mapping(address => bool) public whitelists;

    /// @notice Total supply of the FX1 Sports Token
    uint256 private _totalSupply = 300_000_000 * 10 ** _decimals;
    /// @notice Timestamp of the token contract launch
    uint256 public launchTime;
    /// @notice Period during which addresses can be added to the whitelist
    uint256 public whitelistPeriod;
    /// @notice Minimum amount of tokens required to trigger an automatic swap for liquidity
    uint256 public swapThreshold;
    /// @notice Maximum amount of tokens that a wallet can hold
    uint256 public maxWalletAmount;
    /// @notice Accumulated amount of tokens reserved for liquidity
    uint256 private accLiquidityAmount;
    /// @notice Accumulated amount of tokens reserved for marketing purposes
    uint256 private accMarketingAmount;

    /// @notice The cumulative fee rate applied to buy transactions
    uint256 public totalBuyFeeRate;
    /// @notice The cumulative fee rate applied to sale transactions
    uint256 public totalSellFeeRate;

    /// @notice Address for receiving marketing-related tax payments
    address public marketingTaxRecv;
    /// @notice Address of the generated token pair
    address public pair;
    /// @notice Address representing a dead wallet
    address constant DEAD = 0x000000000000000000000000000000000000dEaD;

    /// @notice Variable indicates whether a liquidity swap is in progress
    bool private inSwapLiquidity;

    /// @notice Name of the FX1 Sports Token
    string private _name = "FXI Sports";
    /// @notice Symbol of the FX1 Sports Token
    string private _symbol = "FXI";

    /// @notice Fixed-point multiplier used for calculations
    uint256 public immutable FIXED_POINT = 1000;
    /// @notice The maximum allowable fee rate
    uint16 public constant MAX_FEE = 100;
    /// @notice Number of decimal places for token values
    uint8 private constant _decimals = 18;

    /// @notice Router for interacting with the Uniswap decentralized exchange
    IUniswapV2Router02 public dexRouter;
    /// @notice Fee rates for buying transactions
    FeeRate public buyfeeRate;
    /// @notice Fee rates for selling transactions
    FeeRate public sellfeeRate;

    /// @notice Constructs the FX1SportsToken contract
    /// @param _param A struct containing various parameters required for the token's configuration
    constructor(
        FeeRate memory _buyfeeRate,
        FeeRate memory _sellfeeRate,
        Param memory _param
    ) checkFeeRates(_buyfeeRate) checkFeeRates(_sellfeeRate) {
        require(
            _param.marketingTaxRecv != address(0),
            "Invalid MarketingTaxRecv address"
        );
        require(_param.dexRouter != address(0), "Invalid dexRouter adddress");
        require(_param.whitelistPeriod > 0, "Invalid whitelistPeriod");
        address sender = msg.sender;
        marketingTaxRecv = _param.marketingTaxRecv;
        dexRouter = IUniswapV2Router02(_param.dexRouter);
        whitelistPeriod = _param.whitelistPeriod;
        buyfeeRate.liquidityFeeRate = _buyfeeRate.liquidityFeeRate;
        buyfeeRate.marketingFeeRate = _buyfeeRate.marketingFeeRate;
        totalBuyFeeRate =
            _buyfeeRate.liquidityFeeRate +
            _buyfeeRate.marketingFeeRate;

        sellfeeRate.liquidityFeeRate = _sellfeeRate.liquidityFeeRate;
        sellfeeRate.marketingFeeRate = _sellfeeRate.marketingFeeRate;
        totalSellFeeRate =
            _sellfeeRate.liquidityFeeRate +
            _sellfeeRate.marketingFeeRate;

        excludedFromFees[sender] = true;
        excludedFromMaxWallet[sender] = true;
        excludedFromMaxWallet[address(this)] = true;
        excludedFromMaxWallet[marketingTaxRecv] = true;
        whitelists[sender] = true;
        whitelists[address(this)] = true;

        _balances[sender] += _totalSupply;
        emit Transfer(address(0), sender, _totalSupply);
        swapThreshold = _totalSupply / 10000; // 0.01%
    }

    receive() external payable {}

    /**
     * @notice A modifier to check and enforce the maximum fee rates for marketing and liquidity
     * @param _fees The structure containing marketing and liquidity fee rates
     */
    modifier checkFeeRates(FeeRate memory _fees) {
        require(
            _fees.marketingFeeRate + _fees.liquidityFeeRate <= MAX_FEE,
            "Max Rate exceeded, please lower value"
        );
        _;
    }

    /// ================================ Functions for ERC20 token ================================ ///

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

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

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

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

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

    function transfer(
        address _recipient,
        uint256 _amount
    ) external override returns (bool) {
        _transfer(msg.sender, _recipient, _amount);
        return true;
    }

    function allowance(
        address _owner,
        address _spender
    ) external view override returns (uint256) {
        return _allowances[_owner][_spender];
    }

    function approve(
        address _spender,
        uint256 _amount
    ) external override returns (bool) {
        _approve(msg.sender, _spender, _amount);
        return true;
    }

    function transferFrom(
        address _sender,
        address _recipient,
        uint256 _amount
    ) external override returns (bool) {
        uint256 currentAllowance = _allowances[_sender][msg.sender];
        require(currentAllowance >= _amount, "Transfer > allowance");
        _approve(_sender, msg.sender, currentAllowance - _amount);
        _transfer(_sender, _recipient, _amount);
        return true;
    }

    /// ================================ External functions ================================ ///

    /**
     * @notice Updates the address of the Uniswap router
     * @param _newRouter The new address of the Uniswap router
     */
    function updateDexRouter(address _newRouter) external onlyOwner {
        require(Address.isContract(_newRouter), "Address is not a contract");
        dexRouter = IUniswapV2Router02(_newRouter);
    }

    /**
     * @notice External function update the Uniswap pair address and adjust exemption settings
     * @param _pair The new Uniswap pair address
     */
    function updatePair(address _pair) external onlyOwner {
        require(_pair != address(0), "Invalid pair address");
        if (pair != address(0)) {
            excludedFromMaxWallet[pair] = false;
            whitelists[pair] = false;
        }
        pair = _pair;
        excludedFromMaxWallet[_pair] = true;
        whitelists[_pair] = true;
    }

    /**
     * @notice External function update the whitelist period
     * @param _newWhiteListPeriod The new duration of the whitelist period in seconds
     */
    function setWhiteListPeriod(
        uint256 _newWhiteListPeriod
    ) external onlyOwner {
        require(_newWhiteListPeriod > 0, "Invalid whitelistPeriod");
        whitelistPeriod = _newWhiteListPeriod;
    }

    /**
     * @notice External function for allows the contract owner to send FX1SportsToken amounts of tokens to multiple recipients
     * @param _recipients An array of recipient addresses to send tokens to
     * @param _amounts An array of corresponding token amounts to be sent to each recipient
     */
    function multiSender(
        address[] memory _recipients,
        uint256[] memory _amounts
    ) external onlyOwner {
        require(_recipients.length == _amounts.length, "Invalid arrays length");

        uint256 totalAmountToSend = 0;
        for (uint256 i = 0; i < _recipients.length; ) {
            require(_recipients[i] != address(0), "Invalid recipient address");
            totalAmountToSend += _amounts[i];

            unchecked {
                i++;
            }
        }

        require(
            _balances[msg.sender] >= totalAmountToSend,
            "Not enough balance to send"
        );
        for (uint256 i = 0; i < _recipients.length; ) {
            _transfer(msg.sender, _recipients[i], _amounts[i]);

            unchecked {
                i++;
            }
        }
    }

    /**
     * @notice External function allows the contract owner to set the launch time of the token
     */
    function setLaunchBegin() external override onlyOwner {
        require(launchTime == 0, "Already launched");
        launchTime = block.timestamp;
    }

    /**
     * @notice External function allows the contract owner to add or remove multiple addresses from the whitelists
     * @param _accounts An array of addresses to be added or removed from the whitelists
     * @param _add A boolean indicating whether to add or remove the addresses from the whitelists
     */
    function updateWhitelists(
        address[] memory _accounts,
        bool _add
    ) external override onlyOwner {
        uint256 length = _accounts.length;
        require(length > 0, "Invalid accounts length");

        for (uint256 i = 0; i < length; ) {
            whitelists[_accounts[i]] = _add;

            unchecked {
                i++;
            }
        }
    }

    /**
     * @notice External function allows the contract owner to exclude or include multiple addresses from the list of addresses exempted from maximum wallet balance limits
     * @param _accounts An array of addresses to be excluded or included in the list
     * @param _add A boolean indicating whether to exclude or include the addresses in the list
     */
    function excludeWalletsFromMaxWallets(
        address[] memory _accounts,
        bool _add
    ) external override onlyOwner {
        uint256 length = _accounts.length;
        require(length > 0, "Invalid length array");
        for (uint256 i = 0; i < length; ) {
            excludedFromMaxWallet[_accounts[i]] = _add;

            unchecked {
                i++;
            }
        }
    }

    /**
     * @notice External function allows the contract owner to exclude or include multiple addresses from the list of addresses exempted from transaction fees
     * @param _accounts An array of addresses to be excluded or included in the list
     * @param _add A boolean indicating whether to exclude or include the addresses in the list
     */
    function excludeWalletsFromFees(
        address[] memory _accounts,
        bool _add
    ) external override onlyOwner {
        uint256 length = _accounts.length;
        require(length > 0, "Invalid length array");
        for (uint256 i = 0; i < length; ) {
            excludedFromFees[_accounts[i]] = _add;

            unchecked {
                i++;
            }
        }
    }

    /**
     * @notice External function allows the contract owner to set a new maximum wallet balance limit
     * @param newLimit The new maximum transfer amount limit to be set
     */
    function setMaxWalletAmount(uint256 newLimit) external override onlyOwner {
        require(newLimit >= (_totalSupply * 10) / 1000, "Min 1% limit");
        maxWalletAmount = newLimit;
    }

    /**
     * @notice External function allows the contract owner to set a new address for the marketing tax wallet
     * @param _marketingTaxWallet The new address to be set as the marketing tax wallet
     */
    function setMarketingTaxWallet(
        address _marketingTaxWallet
    ) external override onlyOwner {
        require(
            _marketingTaxWallet != address(0),
            "Invalid marketingTaxWallet address"
        );
        marketingTaxRecv = _marketingTaxWallet;
    }

    /**
     * @notice This function allows the contract owner to update the fee rates for buy operations
     * @param _marketingBuyFeeRate New marketing fee rate for buy operations
     * @param _liquidityBuyFeeRate New liquidity fee rate for buy operations
     */
    function updateBuyFeeRate(
        uint16 _marketingBuyFeeRate,
        uint16 _liquidityBuyFeeRate
    ) external override onlyOwner {
        require(
            _marketingBuyFeeRate + _liquidityBuyFeeRate <= MAX_FEE,
            "Max Rate exceeded, please lower value"
        );
        buyfeeRate.marketingFeeRate = _marketingBuyFeeRate;
        buyfeeRate.liquidityFeeRate = _liquidityBuyFeeRate;
        totalBuyFeeRate = _marketingBuyFeeRate + _liquidityBuyFeeRate;
    }

    /**
     * @notice This function allows the contract owner to update the fee rates for sell operations
     * @param _marketingSellFeeRate New marketing fee rate for sell operations
     * @param _liquiditySellFeeRate New liquidity fee rate for sell operations
     */
    function updateSellFeeRate(
        uint16 _marketingSellFeeRate,
        uint16 _liquiditySellFeeRate
    ) external override onlyOwner {
        require(
            _marketingSellFeeRate + _liquiditySellFeeRate <= MAX_FEE,
            "Max Rate exceeded, please lower value"
        );
        sellfeeRate.marketingFeeRate = _marketingSellFeeRate;
        sellfeeRate.liquidityFeeRate = _liquiditySellFeeRate;
        totalSellFeeRate = _marketingSellFeeRate + _liquiditySellFeeRate;
    }

    /**
     * @notice External function allows the contract owner to set a new swap threshold value
     * @param _swapThreshold The new swap threshold value to be set
     */
    function setSwapThreshold(
        uint256 _swapThreshold
    ) external override onlyOwner {
        require(_swapThreshold > 0, "Invalid swapThreshold");
        swapThreshold = _swapThreshold;
    }

    /// ================================ Internal functions ================================ ///

    /**
     * @notice Internal function to perform token transfer between two addresses, subject to various checks and conditions
     * @param _sender The address from which tokens are being transferred
     * @param _recipient The address to which tokens are being transferred
     * @param _amount The amount of tokens being transferred
     */
    function _transfer(
        address _sender,
        address _recipient,
        uint256 _amount
    ) internal {
        require(_sender != address(0), "Transfer from zero address");
        require(_recipient != address(0), "Transfer to zero address");
        require(_amount > 0, "Zero amount");
        require(_balances[_sender] >= _amount, "Not enough amount to transfer");
        require(_sender == owner() || launchTime != 0, "Not launched yet");
        if (block.timestamp < launchTime + whitelistPeriod) {
            require(whitelists[_recipient], "only whitelist");
        }
        if (maxWalletAmount > 0) {
            require(
                excludedFromMaxWallet[_recipient] ||
                    _balances[_recipient] + _amount <= maxWalletAmount,
                "Exceeds to maxWalletAmount"
            );
        }
        if (
            inSwapLiquidity ||
            excludedFromFees[_recipient] ||
            excludedFromFees[_sender]
        ) {
            _basicTransfer(_sender, _recipient, _amount);
            emit Transfer(_sender, _recipient, _amount);
            return;
        }
        if (pair != address(0)) {
            if (_sender == pair) {
                // buy
                _taxonBuyTransfer(_sender, _recipient, _amount);
            } else {
                _swapBack();

                if (_recipient == pair) {
                    // sell
                    _taxonSellTransfer(_sender, _recipient, _amount);
                } else {
                    _basicTransfer(_sender, _recipient, _amount);
                }
            }
        }

        emit Transfer(_sender, _recipient, _amount);
    }

    /**
     * @notice Internal function to swap excess tokens in the contract back to ETH and manage liquidity and fees
     */
    function _swapBack() internal {
        uint256 accTotalAmount = accLiquidityAmount + accMarketingAmount;
        if (accTotalAmount <= swapThreshold) {
            return;
        }
        inSwapLiquidity = true;
        uint256 swapAmountForLiquidity = accLiquidityAmount / 2;
        uint256 swapAmount = accTotalAmount - swapAmountForLiquidity;
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = dexRouter.WETH();
        _approve(address(this), address(dexRouter), swapAmount);
        dexRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            swapAmount,
            0,
            path,
            address(this),
            block.timestamp + 30 minutes
        );
        uint256 swappedETHAmount = address(this).balance;
        require(swappedETHAmount > 0, "Too small token for swapBack");
        uint256 ethForLiquidity = (swappedETHAmount * swapAmountForLiquidity) /
            swapAmount;

        if (ethForLiquidity > 0) {
            uint256 amountForLiquidity = accLiquidityAmount -
                swapAmountForLiquidity;
            _approve(address(this), address(dexRouter), amountForLiquidity);
            dexRouter.addLiquidityETH{value: ethForLiquidity}(
                address(this),
                amountForLiquidity,
                0,
                0,
                0x000000000000000000000000000000000000dEaD,
                block.timestamp + 30 minutes
            );
            swappedETHAmount -= ethForLiquidity;
        }

        _transferETH(marketingTaxRecv, swappedETHAmount);

        accLiquidityAmount = 0;
        accMarketingAmount = 0;
        inSwapLiquidity = false;
    }

    /**
     * @notice Internal function to handle transfers when tokens are being sold
     * @param _sender The address of the sender (seller)
     * @param _recipient The address of the recipient (buyer)
     * @param _amount The amount of tokens being transferred
     */
    function _taxonSellTransfer(
        address _sender,
        address _recipient,
        uint256 _amount
    ) internal {
        (
            uint256 marketingFeeRate,
            uint256 liquidityFeeRate
        ) = _getSellFeeRate();

        uint256 marketingFeeAmount = (_amount * marketingFeeRate) / FIXED_POINT;
        uint256 liquidityFeeAmount = (_amount * liquidityFeeRate) / FIXED_POINT;
        uint256 recvAmount = _amount -
            (marketingFeeAmount + liquidityFeeAmount);

        _balances[_sender] -= _amount;
        _balances[_recipient] += recvAmount;
        _balances[address(this)] += (marketingFeeAmount + liquidityFeeAmount);
        accLiquidityAmount += liquidityFeeAmount;
        accMarketingAmount += marketingFeeAmount;
    }

    /**
     * @notice Internal function to handle transfers when tokens are being bought
     * @param _sender The address of the sender (buyer)
     * @param _recipient The address of the recipient (seller)
     * @param _amount The amount of tokens being transferred
     */
    function _taxonBuyTransfer(
        address _sender,
        address _recipient,
        uint256 _amount
    ) internal {
        (uint256 marketingFeeRate, uint256 liquidityFeeRate) = _getBuyFeeRate();

        uint256 marketingFeeAmount = (_amount * marketingFeeRate) / FIXED_POINT;
        uint256 liquidityFeeAmount = (_amount * liquidityFeeRate) / FIXED_POINT;
        uint256 recvAmount = _amount -
            (marketingFeeAmount + liquidityFeeAmount);

        _balances[_sender] -= _amount;
        _balances[_recipient] += recvAmount;
        _balances[address(this)] += (marketingFeeAmount + liquidityFeeAmount);
        accLiquidityAmount += liquidityFeeAmount;
        accMarketingAmount += marketingFeeAmount;
    }

    /**
     * @notice Internal function to perform a basic transfer of tokens between two addresses
     * @param _sender The address of the sender
     * @param _recipient The address of the recipient
     * @param _amount The amount of tokens to transfer
     */
    function _basicTransfer(
        address _sender,
        address _recipient,
        uint256 _amount
    ) internal {
        _balances[_sender] -= _amount;
        _balances[_recipient] += _amount;
    }

    /**
     * @notice Internal function to get the fee rates for selling tokens based on the current time period after launch
     */
    function _getSellFeeRate()
        internal
        view
        returns (uint256 _marketingFeeRate, uint256 _liquidityFeeRate)
    {
        return (sellfeeRate.marketingFeeRate, sellfeeRate.liquidityFeeRate);
    }

    /**
     * @notice Internal function to get the fee rates for buying tokens based on the current time period after launch
     */
    function _getBuyFeeRate()
        internal
        view
        returns (uint256 _marketingFeeRate, uint256 _liquidityFeeRate)
    {
        return (buyfeeRate.marketingFeeRate, buyfeeRate.liquidityFeeRate);
    }

    /**
     * @notice Internal function to transfer ETH to a specified recipient
     * @param _recipient The address of the recipient to which ETH should be transferred
     * @param _amount The amount of ETH to transfer
     */
    function _transferETH(address _recipient, uint256 _amount) internal {
        if (_amount == 0) return;
        (bool sent, ) = _recipient.call{value: _amount}("");
        require(sent, "Sending ETH failed");
    }

    /// ================================ Private functions ================================ ///

    /**
     * @notice Private function to set the allowance amount that `_spender` can spend on behalf of `_owner`
     * @param _owner The address that approves spending
     * @param _spender The address that is allowed to spend
     * @param _amount The amount of tokens that `_spender` is allowed to spend
     */
    function _approve(
        address _owner,
        address _spender,
        uint256 _amount
    ) private {
        require(_owner != address(0), "Approve from zero");
        require(_spender != address(0), "Approve to zero");
        _allowances[_owner][_spender] = _amount;
        emit Approval(_owner, _spender, _amount);
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/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() {
        _transferOwnership(_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 {
        _transferOwnership(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");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// File: contracts/interfaces/IUniswapV2Factory.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

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: contracts/interfaces/IUniswapV2Router02.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

interface IUniswapV2Router01 {
    function factory() external pure returns (address);

    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    )
        external
        payable
        returns (uint amountToken, uint amountETH, uint liquidity);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);

    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint amountA, uint amountB);

    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint amountToken, uint amountETH);

    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactETHForTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function swapTokensForExactETH(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapETHForExactTokens(
        uint amountOut,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function quote(
        uint amountA,
        uint reserveA,
        uint reserveB
    ) external pure returns (uint amountB);

    function getAmountOut(
        uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) external pure returns (uint amountOut);

    function getAmountIn(
        uint amountOut,
        uint reserveIn,
        uint reserveOut
    ) external pure returns (uint amountIn);

    function getAmountsOut(
        uint amountIn,
        address[] calldata path
    ) external view returns (uint[] memory amounts);

    function getAmountsIn(
        uint amountOut,
        address[] calldata path
    ) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

// File: contracts/interfaces/IFXISportsToken.sol



//SPDX-License-Identifier: None

pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title FXI Sports Token
/// @title https://fx1.io/
/// @title https://t.me/fx1_sports_portal
/// @author https://PROOFplatform.io
/// @author https://5thWeb.io

interface IFXISportsToken is IERC20 {
    struct Param {
        address marketingTaxRecv;
        address dexRouter;
        uint256 whitelistPeriod;
    }

    struct FeeRate {
        uint256 marketingFeeRate;
        uint256 liquidityFeeRate;
    }

    /// @notice Locks trading until called. Cannont be called twice.
    /// @dev Only owner can call this function.
    function setLaunchBegin() external;

    /// @notice Add/Remove whitelists.
    /// @dev Only owner can call this function.
    /// @param _accounts The address of whitelists.
    /// @param _add True/False = Add/Remove
    function updateWhitelists(address[] memory _accounts, bool _add) external;

    /// @notice Add/Remove wallets to excludedMaxWallet.
    /// @dev Only owner can call this function.
    /// @param _accounts The address of accounts.
    /// @param _add True/False = Add/Remove
    function excludeWalletsFromMaxWallets(
        address[] memory _accounts,
        bool _add
    ) external;

    /// @notice Add/Remove wallets to excludedFromFees.
    /// @dev Only owner can call this function.
    /// @param _accounts The address of accounts.
    /// @param _add True/False = Add/Remove
    function excludeWalletsFromFees(
        address[] memory _accounts,
        bool _add
    ) external;

    /// @notice Set maxWalletAmount.
    /// @dev Only owner can call this function.
    /// @param _maxWalletAmount New maxWalletAmount.
    function setMaxWalletAmount(uint256 _maxWalletAmount) external;

    /// @notice Set marketingTaxRecipient wallet address.
    /// @dev Only owner can call this function.
    /// @param _marketingTaxWallet The address of marketingTaxRecipient wallet.
    function setMarketingTaxWallet(address _marketingTaxWallet) external;

    /// @notice UpdateBuyFeeRate.
    /// @dev Only owner can call this function.
    /// @dev Max Rate of 100(10%) 10 = 1%
    /// @param _marketingBuyFeeRate New MarketingBuyFeeRate.
    /// @param _liquidityBuyFeeRate New LiquidityBuyFeeRate.
    function updateBuyFeeRate(
        uint16 _marketingBuyFeeRate,
        uint16 _liquidityBuyFeeRate
    ) external;

    /// @notice UpdateSellFeeRate.
    /// @dev Only owner can call this function.
    /// @dev Max Rate of 100(10%) 10 = 1%
    /// @param _marketingSellFeeRate New MarketingSellFeeRate.
    /// @param _liquiditySellFeeRate New LiquiditySellFeeRate.
    function updateSellFeeRate(
        uint16 _marketingSellFeeRate,
        uint16 _liquiditySellFeeRate
    ) external;

    /// @notice Set swapThreshold.
    /// @dev Only owner can call this function.
    /// @param _swapThreshold New swapThreshold amount.
    function setSwapThreshold(uint256 _swapThreshold) external;
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

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

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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);

    /**
     * @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 `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, 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 `from` to `to` 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 from,
        address to,
        uint256 amount
    ) external returns (bool);
}