Sonic Blaze Testnet

Contract Diff Checker

Contract Name:
SaraDEX

Contract Source Code:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol)

pragma solidity ^0.8.20;

/**
 * @dev External interface of AccessControl declared to support ERC-165 detection.
 */
interface IAccessControl {
    /**
     * @dev The `account` is missing a role.
     */
    error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);

    /**
     * @dev The caller of a function is not the expected one.
     *
     * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
     */
    error AccessControlBadConfirmation();

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
     * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     */
    function renounceRole(bytes32 role, address callerConfirmation) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling 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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./SaraLiquidityManager.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract SaraDEX is Ownable {
    using SafeERC20 for IERC20;

    address public sonicToken; // Address of Sonic's native token (S)
    SaraLiquidityManager public liquidityManager;
    uint256 public swapFee = 30; // 0.3% fee (divided by 10,000 for precision)

    // Add anti-bot protection
    mapping(address => uint256) public lastSwapTimestamp;
    uint256 public constant MIN_TIME_BETWEEN_SWAPS = 1 minutes;
    uint256 public constant MAX_SWAP_AMOUNT_PERCENT = 5; // 5% of reserves

    // Add fee storage tracking
    mapping(address => uint256) public storedFees;

    uint256 public constant MAX_SINGLE_SWAP = 1000 * 1e18; // 1000 tokens per swap

    // Change from constant to regular state variable
    uint256 public AUTO_REDEPLOY_THRESHOLD = 1000 * 1e18; // 1000 tokens initial value

    // Change from constant to immutable/variable
    uint256 public immutable DEFAULT_MAX_SLIPPAGE = 500; // 5%
    uint256 public ABSOLUTE_MAX_SLIPPAGE = 1000; // 10% initial cap
    uint256 public constant MAXIMUM_ALLOWED_SLIPPAGE = 2000; // 20% absolute maximum

    // Add error messages as constants
    string private constant ERR_SAME_VALUE = "New slippage same as current";
    string private constant ERR_LOW_SLIPPAGE = "Cannot be lower than default";
    string private constant ERR_HIGH_SLIPPAGE = "Cannot exceed 20%";

    event SwapExecuted(
        address indexed user,
        address indexed creatorToken,
        uint256 amountIn,
        uint256 amountOut,
        uint256 fee
    );

    event FeesWithdrawn(address indexed token, uint256 amount);

    // Add event for slippage updates
    event MaxSlippageUpdated(
        uint256 oldSlippage,
        uint256 newSlippage,
        address indexed updater
    );

    // Add new events at the top
    event SlippageUpdated(
        uint256 oldSlippage, 
        uint256 newSlippage, 
        address indexed updatedBy
    );

    event SwapFeesUpdated(
        uint256 oldFee,
        uint256 newFee,
        address indexed updatedBy
    );

    event LiquidityManagerUpdated(
        address indexed oldManager,
        address indexed newManager,
        address indexed updatedBy
    );

    event AutoRedeployThresholdUpdated(
        uint256 oldThreshold,
        uint256 newThreshold,
        address indexed updatedBy
    );

    // Add state variables at the top
    bool public paused;
    uint256 public lastPauseTimestamp;

    // Add pause-related events
    event ContractPaused(
        address indexed by,
        uint256 timestamp
    );

    event ContractUnpaused(
        address indexed by,
        uint256 timestamp
    );

    event EmergencyWithdraw(
        address indexed token,
        uint256 amount,
        address indexed to,
        uint256 timestamp
    );

    // Add both pause-related modifiers
    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }

    modifier whenPaused() {
        require(paused, "Contract not paused");
        _;
    }

    // Add at the top with other constants
    uint256 public constant MIN_LIQUIDITY = 1000 * 1e18; // 1000 tokens minimum liquidity

    constructor(
        address _sonicToken, 
        address payable _liquidityManager
    ) Ownable(msg.sender) {  // Pass msg.sender to Ownable constructor
        sonicToken = _sonicToken;
        liquidityManager = SaraLiquidityManager(_liquidityManager);
    }

    function swapCreatorTokenForS(
        address creatorToken, 
        uint256 amountIn, 
        uint256 minAmountOut,
        uint256 maxSlippage
    ) external payable whenNotPaused {
        // Add anti-bot check
        require(block.timestamp >= lastSwapTimestamp[msg.sender] + MIN_TIME_BETWEEN_SWAPS, "Too many swaps");
        
        // Get reserves
        (uint256 reserveCreator, uint256 reserveS) = liquidityManager.getReserves(creatorToken);
        
        // Add liquidity checks
        require(amountIn <= reserveCreator, "Insufficient liquidity in pool");
        require(reserveS > 0, "No S token liquidity");
        
        // Add large swap protection
        require(amountIn <= reserveCreator * MAX_SWAP_AMOUNT_PERCENT / 100, "Swap too large");

        require(amountIn > 0, "Invalid amount");
        require(IERC20(creatorToken).balanceOf(msg.sender) >= amountIn, "Insufficient balance");

        // Calculate output amount
        uint256 amountOut = getAmountOut(amountIn, reserveCreator, reserveS);
        require(amountOut >= minAmountOut, "Slippage too high");

        // Calculate fee
        uint256 fee = (amountOut * swapFee) / 10000;
        uint256 amountOutAfterFee = amountOut - fee;

        // Split large swaps
        if (amountIn > MAX_SINGLE_SWAP) {
            uint256 remainingAmount = amountIn;
            uint256 totalReceived = 0;
            
            while (remainingAmount > 0) {
                uint256 currentSwapAmount = remainingAmount > MAX_SINGLE_SWAP ? 
                    MAX_SINGLE_SWAP : remainingAmount;
                
                // Execute partial swap
                uint256 received = _executeSwap(
                    creatorToken,
                    currentSwapAmount,
                    maxSlippage
                );
                
                totalReceived += received;
                remainingAmount -= currentSwapAmount;
            }
            
            require(totalReceived >= minAmountOut, "Slippage too high");
            return;
        }

        // Transfer creator tokens from user
        IERC20(creatorToken).safeTransferFrom(msg.sender, address(this), amountIn);

        // Update state
        storedFees[sonicToken] += fee;

        // Check for auto-redeployment
        if (storedFees[sonicToken] >= AUTO_REDEPLOY_THRESHOLD) {
            liquidityManager.autoRedeployFees(sonicToken);
        }

        // Transfer S tokens to user
        (bool success,) = msg.sender.call{value: amountOutAfterFee}("");
        require(success, "S token transfer failed");

        // Update timestamp and emit event
        lastSwapTimestamp[msg.sender] = block.timestamp;
        emit SwapExecuted(msg.sender, creatorToken, amountIn, amountOutAfterFee, fee);
    }

    function _executeSwap(
        address creatorToken,
        uint256 amountIn,
        uint256 maxSlippage
    ) internal returns (uint256) {
        // Validate slippage first to save gas
        require(maxSlippage <= ABSOLUTE_MAX_SLIPPAGE, "Slippage too high");
        uint256 slippageUsed = maxSlippage > 0 ? maxSlippage : DEFAULT_MAX_SLIPPAGE;
        require(slippageUsed >= DEFAULT_MAX_SLIPPAGE, "Invalid slippage");

        // Check approvals before any calculations
        require(
            IERC20(creatorToken).allowance(msg.sender, address(this)) >= amountIn,
            "Insufficient creator token allowance"
        );

        // Get reserves and check liquidity
        (uint256 reserveCreator, uint256 reserveS) = liquidityManager.getReserves(creatorToken);
        require(amountIn <= reserveCreator, "Insufficient liquidity in pool");
        require(reserveS > amountIn, "Insufficient S token liquidity");
        
        // Calculate output amount
        uint256 amountOut = getAmountOut(amountIn, reserveCreator, reserveS);
        uint256 minAcceptableOutput = amountOut - ((amountOut * slippageUsed) / 10000);
        
        // Apply swap fee
        uint256 fee = (amountOut * swapFee) / 10000;
        uint256 amountOutAfterFee = amountOut - fee;

        // Check slippage tolerance
        require(amountOutAfterFee >= minAcceptableOutput, "Slippage too high");

        // Check native S token balance
        require(address(this).balance >= amountOutAfterFee, "Insufficient S balance");

        // Execute transfers
        IERC20(creatorToken).safeTransferFrom(msg.sender, address(this), amountIn);
        
        // Transfer native S
        (bool success,) = msg.sender.call{value: amountOutAfterFee}("");
        require(success, "S token transfer failed");

        // Update state
        storedFees[sonicToken] += fee;

        // Check for auto-redeployment
        if (storedFees[sonicToken] >= AUTO_REDEPLOY_THRESHOLD) {
            liquidityManager.autoRedeployFees(sonicToken);
        }

        // Update timestamp and emit event
        lastSwapTimestamp[msg.sender] = block.timestamp;
        emit SwapExecuted(msg.sender, creatorToken, amountIn, amountOutAfterFee, fee);

        return amountOutAfterFee;
    }

    /**
     * @dev Calculates output amount based on constant product formula
     * @param amountIn Amount of input tokens
     * @param reserveIn Reserve of input token
     * @param reserveOut Reserve of output token
     * @return Amount of output tokens
     */
    function getAmountOut(
        uint256 amountIn, 
        uint256 reserveIn, 
        uint256 reserveOut
    ) public pure returns (uint256) {
        require(amountIn > 0, "Invalid input amount");
        require(reserveIn > 0 && reserveOut > 0, "Invalid reserves");

        // Use more precise fee calculation (0.25% fee = 9975/10000)
        uint256 amountInWithFee = amountIn * 9975;
        
        // Calculate with higher precision
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * 10000) + amountInWithFee;
        
        // Prevent division by zero (though require above should catch this)
        require(denominator > 0, "Invalid calculation");
        
        return numerator / denominator;
    }

    function withdrawStoredFees(address token, uint256 amount) external onlyOwner {
        require(storedFees[token] >= amount, "Insufficient stored fees");
        storedFees[token] -= amount;

        if (isNativeS(token)) {
            (bool success,) = msg.sender.call{value: amount}("");
            require(success, "S token transfer failed");
        } else {
            IERC20(token).safeTransfer(msg.sender, amount);
        }

        emit FeesWithdrawn(token, amount);
    }

    /**
     * @dev Updates the maximum allowed slippage with optimizations
     * @param newMaxSlippage New maximum slippage in basis points (e.g., 1000 = 10%)
     */
    function updateMaxSlippage(uint256 newMaxSlippage) external onlyOwner {
        // Check if value is actually changing
        require(newMaxSlippage != ABSOLUTE_MAX_SLIPPAGE, ERR_SAME_VALUE);
        
        // Validate bounds
        require(newMaxSlippage >= DEFAULT_MAX_SLIPPAGE, ERR_LOW_SLIPPAGE);
        require(newMaxSlippage <= MAXIMUM_ALLOWED_SLIPPAGE, ERR_HIGH_SLIPPAGE);
        
        // Store old value for event
        uint256 oldSlippage = ABSOLUTE_MAX_SLIPPAGE;
        
        // Update slippage
        ABSOLUTE_MAX_SLIPPAGE = newMaxSlippage;
        
        // Emit event with old and new values
        emit SlippageUpdated(
            oldSlippage,
            newMaxSlippage,
            msg.sender
        );
    }

    /**
     * @dev View function to validate potential slippage value
     * @param slippage Slippage value to validate
     * @return bool Valid or not
     * @return string Memory reason if invalid
     */
    function validateSlippage(
        uint256 slippage
    ) external view returns (bool, string memory) {
        if (slippage == ABSOLUTE_MAX_SLIPPAGE) return (false, ERR_SAME_VALUE);
        if (slippage < DEFAULT_MAX_SLIPPAGE) return (false, ERR_LOW_SLIPPAGE);
        if (slippage > MAXIMUM_ALLOWED_SLIPPAGE) return (false, ERR_HIGH_SLIPPAGE);
        return (true, "");
    }

    /**
     * @dev View function to get current slippage settings
     */
    function getSlippageSettings() external view returns (
        uint256 defaultSlippage,
        uint256 currentMaxSlippage,
        uint256 absoluteMaximum
    ) {
        return (
            DEFAULT_MAX_SLIPPAGE,
            ABSOLUTE_MAX_SLIPPAGE,
            MAXIMUM_ALLOWED_SLIPPAGE
        );
    }

    // Add receive function to accept native S tokens
    receive() external payable {}

    // Add helper function
    function isNativeS(address token) internal pure returns (bool) {
        return token == address(0) || token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    }

    // Add function to update swap fee
    function updateSwapFee(uint256 newFee) external onlyOwner {
        require(newFee > 0 && newFee <= 500, "Invalid fee"); // Max 5%
        uint256 oldFee = swapFee;
        swapFee = newFee;
        emit SwapFeesUpdated(oldFee, newFee, msg.sender);
    }

    // Add function to update liquidity manager
    function updateLiquidityManager(address payable newManager) external onlyOwner {
        require(newManager != address(0), "Invalid manager address");
        address oldManager = address(liquidityManager);
        liquidityManager = SaraLiquidityManager(newManager);
        emit LiquidityManagerUpdated(oldManager, newManager, msg.sender);
    }

    // Add function to update auto redeploy threshold
    function updateAutoRedeployThreshold(uint256 newThreshold) external onlyOwner {
        require(newThreshold >= MIN_LIQUIDITY, "Below minimum liquidity");
        uint256 oldThreshold = AUTO_REDEPLOY_THRESHOLD;
        AUTO_REDEPLOY_THRESHOLD = newThreshold;
        emit AutoRedeployThresholdUpdated(oldThreshold, newThreshold, msg.sender);
    }

    // Add view function to get current fee rate
    function getFeeRate() external pure returns (
        uint256 feeNumerator,
        uint256 feeDenominator,
        string memory description
    ) {
        return (
            25,  // 0.25%
            10000,
            "0.25% fee (25/10000)"
        );
    }

    // Add pause functions
    function pause() external onlyOwner {
        require(!paused, "Already paused");
        paused = true;
        lastPauseTimestamp = block.timestamp;
        emit ContractPaused(msg.sender, block.timestamp);
    }

    function unpause() external onlyOwner {
        require(paused, "Not paused");
        paused = false;
        emit ContractUnpaused(msg.sender, block.timestamp);
    }

    // Add emergency withdraw function
    function emergencyWithdraw(
        address token,
        uint256 amount,
        address payable to
    ) external onlyOwner whenPaused {
        require(to != address(0), "Invalid recipient");
        
        if (isNativeS(token)) {
            require(address(this).balance >= amount, "Insufficient S balance");
            (bool success,) = to.call{value: amount}("");
            require(success, "S token transfer failed");
        } else {
            require(
                IERC20(token).balanceOf(address(this)) >= amount,
                "Insufficient token balance"
            );
            IERC20(token).safeTransfer(to, amount);
        }

        emit EmergencyWithdraw(token, amount, to, block.timestamp);
    }

    // Add view function to get pause status
    function getPauseStatus() external view returns (
        bool isPaused,
        uint256 pauseDuration,
        uint256 lastPause
    ) {
        return (
            paused,
            paused ? block.timestamp - lastPauseTimestamp : 0,
            lastPauseTimestamp
        );
    }
}

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract SaraLiquidityManager is Ownable, AccessControl {
    using SafeERC20 for IERC20;

    mapping(address => uint256) public creatorTokenReserves;
    mapping(address => uint256) public sonicReserves;
    mapping(address => uint256) public collectedFees;

    address public sonicToken;
    
    uint256 public constant REBALANCE_INTERVAL = 24 hours;
    uint256 public lastRebalanceTimestamp;
    uint256 public constant ENGAGEMENT_THRESHOLD = 1000; // 10% in basis points

    struct EngagementMetrics {
        uint256 lastSubscriberCount;
        uint256 smoothedSubscriberCount;
        uint256 lastUpdateTime;
        uint256 updateCount;
    }
    
    mapping(address => EngagementMetrics) public tokenEngagement;

    // Add constant for minimum liquidity
    uint256 public constant MIN_LIQUIDITY = 1000 * 1e18; // 1000 tokens minimum liquidity

    // Add new event
    event FeesRedeployed(
        address indexed token,
        uint256 amount,
        address indexed targetPool
    );

    // Add minimum fee threshold for redeployment
    uint256 public constant MIN_FEES_FOR_REDEPLOY = 100 * 1e18; // 100 tokens

    // Add new struct for token price discovery
    struct PriceDiscoveryData {
        uint256 initialSubscribers;
        uint256 currentSubscribers;
        uint256 observationStartTime;
        bool isInDiscovery;
        uint256[] engagementSnapshots;
    }
    
    // Add mapping for price discovery
    mapping(address => PriceDiscoveryData) public priceDiscovery;
    
    // Constants for price discovery
    uint256 public constant DISCOVERY_PERIOD = 24 hours;
    uint256 public constant SNAPSHOT_INTERVAL = 4 hours;
    uint256 public constant MAX_SNAPSHOTS = 6; // 24 hours / 4 hours
    
    // Events
    event PriceDiscoveryStarted(address indexed token, uint256 initialSubscribers);
    event PriceDiscoveryCompleted(
        address indexed token, 
        uint256 finalPrice,
        uint256 initialLiquidity
    );
    event EngagementSnapshotTaken(
        address indexed token,
        uint256 subscribers,
        uint256 timestamp
    );

    // Add array to track all pools
    address[] public trackedPools;
    mapping(address => bool) public isTrackedPool;

    // Add event for pool tracking
    event PoolTracked(address indexed pool);
    event PoolUntracked(address indexed pool);

    // Add constant for minimum token price
    uint256 public constant MIN_TOKEN_PRICE = 0.1e18; // 0.1 S tokens minimum price

    // Add price protection constants
    uint256 public constant MAX_PRICE_MULTIPLIER = 3; // 3x cap on price increase
    uint256 public constant BASE_PRICE = 1e18; // 1 S token base price

    // Add new event for auto fee redeployment
    event AutoFeesRedeployed(
        address indexed token,
        uint256 amount,
        address indexed targetPool,
        uint256 timestamp
    );

    // Add constants for smoothing
    uint256 private constant WEIGHT_PREVIOUS = 80;
    uint256 private constant WEIGHT_NEW = 20;
    uint256 private constant REBALANCE_THRESHOLD = 110; // 10% above smoothed value
    uint256 private constant MIN_UPDATES_BEFORE_REBALANCE = 3;

    // Add event for smoothed metrics
    event EngagementSmoothed(
        address indexed token,
        uint256 rawCount,
        uint256 smoothedCount,
        uint256 timestamp
    );

    // Add trading activity tracking
    struct PoolActivity {
        uint256 lastTradeTimestamp;
        uint256 tradingVolume24h;
        uint256 lastVolumeUpdateTime;
    }
    
    mapping(address => PoolActivity) public poolActivity;
    
    // Add constants for activity checks
    uint256 public constant ACTIVITY_THRESHOLD = 7 days;
    uint256 public constant MIN_24H_VOLUME = 1000 * 1e18; // 1000 tokens minimum volume
    
    // Add event for activity updates
    event PoolActivityUpdated(
        address indexed pool,
        uint256 volume24h,
        uint256 timestamp
    );

    // Add error messages as constants
    string private constant ERR_NO_FEES = "No fees available to redeploy";
    string private constant ERR_BELOW_MIN = "Below minimum fee threshold";

    // Add DEX reference
    address public dex;
    
    // Define roles as public constants
    bytes32 public constant DEX_ROLE = keccak256("DEX_ROLE");

    // Add state variables for tracking most needy pool
    struct PoolNeedInfo {
        address pool;
        uint256 score;
        uint256 lastUpdateTime;
    }

    PoolNeedInfo public mostNeededPool;
    uint256 public constant POOL_NEED_UPDATE_INTERVAL = 1 hours;

    // Add event for pool need updates
    event PoolNeedUpdated(
        address indexed pool,
        uint256 score,
        uint256 timestamp
    );

    constructor(address _sonicToken) Ownable(msg.sender) {
        sonicToken = _sonicToken;
        
        // Grant roles to deployer
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(DEX_ROLE, msg.sender);
    }

    event LiquidityAdded(address indexed creatorToken, uint256 amountCreator, uint256 amountS);
    event LiquidityRemoved(address indexed creatorToken, uint256 amountCreator, uint256 amountS);
    event FeesCollected(address indexed token, uint256 amount);
    event LiquidityRebalanced(address indexed token, uint256 newReserveS);
    event FeesWithdrawn(address indexed token, uint256 amount);

    // Add function to track new pool
    function addPoolToTracking(address pool) internal {
        if (!isTrackedPool[pool]) {
            trackedPools.push(pool);
            isTrackedPool[pool] = true;
            emit PoolTracked(pool);
        }
    }

    // Add helper function
    function isNativeS(address token) internal pure returns (bool) {
        return token == address(0) || token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    }

    /**
     * @dev Adds liquidity to a pool with proper validation
     * @param creatorToken The creator token address
     * @param amountCreator Amount of creator tokens to add
     * @param amountS Amount of S tokens to add
     */
    function addLiquidity(
        address creatorToken, 
        uint256 amountCreator, 
        uint256 amountS
    ) external payable onlyOwner {
        // Validate inputs
        require(amountCreator > 0 && amountS > 0, "Invalid liquidity amounts");
        require(msg.value == amountS, "Incorrect S amount sent");

        // Check creator token allowance first
        require(
            IERC20(creatorToken).allowance(msg.sender, address(this)) >= amountCreator,
            "Insufficient creator token allowance"
        );

        // Check creator token balance
        require(
            IERC20(creatorToken).balanceOf(msg.sender) >= amountCreator,
            "Insufficient creator token balance"
        );

        // Check minimum liquidity requirements
        require(
            creatorTokenReserves[creatorToken] + amountCreator >= MIN_LIQUIDITY,
            "Below minimum liquidity"
        );
        require(
            sonicReserves[creatorToken] + amountS >= MIN_LIQUIDITY,
            "Below minimum liquidity"
        );

        // Effects - Update reserves
        creatorTokenReserves[creatorToken] += amountCreator;
        sonicReserves[creatorToken] += amountS;

        // Track pool if new
        addPoolToTracking(creatorToken);

        // Interactions - Transfer creator tokens last
        IERC20(creatorToken).safeTransferFrom(msg.sender, address(this), amountCreator);

        emit LiquidityAdded(creatorToken, amountCreator, amountS);
    }

    // Update removeLiquidity
    function removeLiquidity(
        address creatorToken, 
        uint256 amountCreator, 
        uint256 amountS
    ) external onlyOwner {
        // Checks
        require(creatorTokenReserves[creatorToken] >= amountCreator, "Insufficient creator token liquidity");
        require(sonicReserves[creatorToken] >= amountS, "Insufficient S token liquidity");
        require(address(this).balance >= amountS, "Insufficient S balance");
        
        // Add minimum liquidity check
        require(
            creatorTokenReserves[creatorToken] - amountCreator >= MIN_LIQUIDITY,
            "Cannot remove all liquidity"
        );
        require(
            sonicReserves[creatorToken] - amountS >= MIN_LIQUIDITY,
            "Cannot remove all liquidity"
        );

        // Effects
        creatorTokenReserves[creatorToken] -= amountCreator;
        sonicReserves[creatorToken] -= amountS;

        // Interactions
        IERC20(creatorToken).safeTransfer(msg.sender, amountCreator);
        (bool success,) = msg.sender.call{value: amountS}("");
        require(success, "S token transfer failed");

        emit LiquidityRemoved(creatorToken, amountCreator, amountS);
    }

    function collectFees(address token, uint256 amount) external {
        collectedFees[token] += amount;
        emit FeesCollected(token, amount);
    }

    /**
     * @dev Returns the current reserves for a creator token pair
     * @param creatorToken The address of the creator token
     * @return creatorReserve The amount of creator tokens in reserve
     * @return sReserve The amount of S tokens in reserve
     */
    function getReserves(
        address creatorToken
    ) external view returns (
        uint256 creatorReserve,  // Amount of creator tokens
        uint256 sReserve         // Amount of S tokens
    ) {
        creatorReserve = creatorTokenReserves[creatorToken];
        sReserve = sonicReserves[creatorToken];
    }

    function withdrawFees(address token, uint256 amount) external onlyOwner {
        // Checks
        require(collectedFees[token] >= amount, "Insufficient fees");

        // Effects
        collectedFees[token] -= amount;

        // Interactions
        IERC20(token).safeTransfer(msg.sender, amount);

        emit FeesWithdrawn(token, amount);
    }

    /**
     * @dev Calculates smoothed engagement using weighted moving average
     */
    function calculateSmoothedEngagement(
        uint256 previous,
        uint256 current
    ) internal pure returns (uint256) {
        return (
            (previous * WEIGHT_PREVIOUS / 100) + 
            (current * WEIGHT_NEW / 100)
        );
    }

    /**
     * @dev Updates engagement metrics with smoothing
     */
    function updateEngagementMetrics(
        address token, 
        uint256 newSubscriberCount
    ) external onlyOwner {
        EngagementMetrics storage metrics = tokenEngagement[token];
        
        // Initialize if first update
        if (metrics.lastSubscriberCount == 0) {
            metrics.lastSubscriberCount = newSubscriberCount;
            metrics.smoothedSubscriberCount = newSubscriberCount;
            metrics.updateCount = 1;
            return;
        }

        // Calculate smoothed value
        uint256 newSmoothedCount = calculateSmoothedEngagement(
            metrics.smoothedSubscriberCount,
            newSubscriberCount
        );

        // Update metrics
        metrics.lastSubscriberCount = newSubscriberCount;
        metrics.smoothedSubscriberCount = newSmoothedCount;
        metrics.lastUpdateTime = block.timestamp;
        metrics.updateCount++;

        emit EngagementSmoothed(
            token,
            newSubscriberCount,
            newSmoothedCount,
            block.timestamp
        );

        // Check if rebalance needed (only after minimum updates)
        if (metrics.updateCount >= MIN_UPDATES_BEFORE_REBALANCE) {
            uint256 rebalanceThreshold = (metrics.smoothedSubscriberCount * REBALANCE_THRESHOLD) / 100;
            
            if (newSubscriberCount > rebalanceThreshold) {
                _rebalanceLiquidity(token, metrics.smoothedSubscriberCount);
            }
        }
    }

    /**
     * @dev View function to get smoothed engagement metrics
     */
    function getSmoothedMetrics(
        address token
    ) external view returns (
        uint256 lastCount,
        uint256 smoothedCount,
        uint256 updateCount,
        bool eligibleForRebalance
    ) {
        EngagementMetrics storage metrics = tokenEngagement[token];
        return (
            metrics.lastSubscriberCount,
            metrics.smoothedSubscriberCount,
            metrics.updateCount,
            metrics.updateCount >= MIN_UPDATES_BEFORE_REBALANCE
        );
    }

    /**
     * @dev Optimized rebalancing with minimum liquidity check
     */
    function _rebalanceLiquidity(address token, uint256 subscriberCount) internal {
        // Skip if pool has insufficient liquidity
        if (sonicReserves[token] < MIN_LIQUIDITY) {
            emit RebalanceSkipped(token, sonicReserves[token], "Insufficient liquidity");
            return;
        }

        // AI-based liquidity calculation
        uint256 newReserveS = calculateOptimalLiquidity(subscriberCount);
        
        // Update liquidity
        sonicReserves[token] = newReserveS;
        
        emit LiquidityRebalanced(token, newReserveS);
    }

    function calculateOptimalLiquidity(uint256 subscriberCount) internal pure returns (uint256) {
        // Initial simple formula, can be enhanced with AI later
        return subscriberCount * 1e18;
    }

    // Daily rebalance check
    function checkAndRebalance(address token) external {
        require(
            block.timestamp >= lastRebalanceTimestamp + REBALANCE_INTERVAL,
            "Too early for rebalance"
        );
        
        EngagementMetrics storage metrics = tokenEngagement[token];
        _rebalanceLiquidity(token, metrics.lastSubscriberCount);
        lastRebalanceTimestamp = block.timestamp;
    }

    /**
     * @dev Redeploys collected fees back into liquidity pools
     * @param token Token to redeploy fees for
     * @param targetPool Pool to add liquidity to
     */
    function redeployFees(
        address token,
        address targetPool
    ) public onlyOwner {
        // Get fee amount first
        uint256 feeAmount = collectedFees[token];
        
        // Validate fees
        require(feeAmount > 0, ERR_NO_FEES);
        require(feeAmount >= MIN_FEES_FOR_REDEPLOY, ERR_BELOW_MIN);

        // Calculate optimal deployment amount
        uint256 deployAmount = calculateOptimalDeployment(targetPool, feeAmount);
        require(deployAmount > 0, "No deployment needed");
        require(deployAmount <= feeAmount, "Deploy amount exceeds available fees");

        // Update state before transfers (CEI pattern)
        collectedFees[token] -= deployAmount;

        // Handle native S token differently
        if (isNativeS(token)) {
            require(address(this).balance >= deployAmount, "Insufficient S balance");
            sonicReserves[targetPool] += deployAmount;
            emit FeesRedeployed(token, deployAmount, targetPool);
            return;
        }

        // Handle creator tokens
        require(
            IERC20(token).balanceOf(address(this)) >= deployAmount,
            "Insufficient token balance"
        );

        uint256 sTokenAmount = calculateEquivalentSAmount(token, deployAmount);
        require(address(this).balance >= sTokenAmount, "Insufficient S balance");
        
        // Update reserves
        creatorTokenReserves[targetPool] += deployAmount;
        sonicReserves[targetPool] += sTokenAmount;

        emit FeesRedeployed(token, deployAmount, targetPool);
    }

    /**
     * @dev Calculates optimal amount of fees to deploy based on pool needs
     */
    function calculateOptimalDeployment(
        address pool,
        uint256 availableFees
    ) public view returns (uint256) {
        // Get current pool metrics
        EngagementMetrics storage metrics = tokenEngagement[pool];
        
        // If pool has low liquidity relative to engagement, deploy more fees
        uint256 currentLiquidity = sonicReserves[pool];
        uint256 optimalLiquidity = calculateOptimalLiquidity(metrics.lastSubscriberCount);
        
        if (currentLiquidity >= optimalLiquidity) {
            return 0; // No deployment needed
        }

        uint256 liquidityNeeded = optimalLiquidity - currentLiquidity;
        return liquidityNeeded < availableFees ? liquidityNeeded : availableFees;
    }

    /**
     * @dev Calculates equivalent S token amount for creator token amount
     */
    function calculateEquivalentSAmount(
        address token,
        uint256 amount
    ) internal view returns (uint256) {
        // Use current pool ratio to calculate equivalent S amount
        uint256 creatorReserve = creatorTokenReserves[token];
        uint256 sReserve = sonicReserves[token];
        
        if (creatorReserve == 0) return amount; // 1:1 for new pools
        
        return (amount * sReserve) / creatorReserve;
    }

    /**
     * @dev Auto-redeploys fees with additional checks
     */
    function autoRedeployFees(address token) external {
        uint256 feeAmount = collectedFees[token];
        
        // Validate fees with clear error messages
        require(feeAmount > 0, ERR_NO_FEES);
        require(feeAmount >= MIN_FEES_FOR_REDEPLOY, ERR_BELOW_MIN);

        // Find optimal pool
        address targetPool = findOptimalRedeploymentPool();
        require(targetPool != address(0), "No suitable pool found");

        // Calculate deployment amount
        uint256 deployAmount = calculateOptimalDeployment(targetPool, feeAmount);
        require(deployAmount > 0, "No deployment needed");
        require(deployAmount <= feeAmount, "Deploy amount exceeds available fees");

        // Emit event before state changes
        emit AutoFeesRedeployed(
            token,
            deployAmount,
            targetPool,
            block.timestamp
        );

        // Call redeployFees with validated amounts
        redeployFees(token, targetPool);
    }

    /**
     * @dev Optimized pool selection using cached value
     */
    function findOptimalRedeploymentPool() internal returns (address) {
        // Return cached value if recent enough
        if (
            mostNeededPool.pool != address(0) &&
            block.timestamp < mostNeededPool.lastUpdateTime + POOL_NEED_UPDATE_INTERVAL
        ) {
            // Verify pool is still valid
            if (
                isTrackedPool[mostNeededPool.pool] &&
                isPoolActive(mostNeededPool.pool)
            ) {
                return mostNeededPool.pool;
            }
        }

        // If cache is stale or invalid, update it
        updateMostNeededPool();
        return mostNeededPool.pool;
    }

    /**
     * @dev Updates the cached most needy pool
     */
    function updateMostNeededPool() internal {
        // Reset current best
        mostNeededPool.pool = address(0);
        mostNeededPool.score = 0;

        // Cache array length
        uint256 poolCount = trackedPools.length;
        if (poolCount == 0) return;

        // Cache current time
        uint256 currentTime = block.timestamp;
        uint256 activityCutoff = currentTime - ACTIVITY_THRESHOLD;

        // Single pass through pools
        for (uint256 i = 0; i < poolCount; i++) {
            address pool = trackedPools[i];
            
            // Skip inactive pools
            PoolActivity memory activity = poolActivity[pool];
            if (
                !isTrackedPool[pool] || 
                activity.lastTradeTimestamp < activityCutoff || 
                activity.tradingVolume24h < MIN_24H_VOLUME
            ) {
                continue;
            }
            
            // Calculate pool score
            uint256 score = calculatePoolScore(pool);
            
            // Update if better score found
            if (score > mostNeededPool.score) {
                mostNeededPool.pool = pool;
                mostNeededPool.score = score;
            }
        }

        // Update timestamp and emit event
        mostNeededPool.lastUpdateTime = currentTime;
        emit PoolNeedUpdated(
            mostNeededPool.pool,
            mostNeededPool.score,
            currentTime
        );
    }

    /**
     * @dev Force update of pool needs (public for external triggers)
     */
    function forcePoolNeedUpdate() external onlyOwner {
        updateMostNeededPool();
    }

    /**
     * @dev View function to get current pool need info
     */
    function getPoolNeedInfo() external view returns (
        address pool,
        uint256 score,
        uint256 lastUpdate,
        bool isStale
    ) {
        return (
            mostNeededPool.pool,
            mostNeededPool.score,
            mostNeededPool.lastUpdateTime,
            block.timestamp >= mostNeededPool.lastUpdateTime + POOL_NEED_UPDATE_INTERVAL
        );
    }

    /**
     * @dev Allows removal of inactive pools from tracking
     */
    function removePoolFromTracking(address pool) external onlyOwner {
        require(isTrackedPool[pool], "Pool not tracked");
        
        // Find and remove from array
        for (uint i = 0; i < trackedPools.length; i++) {
            if (trackedPools[i] == pool) {
                // Move last element to this position (unless we're at the end)
                if (i != trackedPools.length - 1) {
                    trackedPools[i] = trackedPools[trackedPools.length - 1];
                }
                trackedPools.pop();
                break;
            }
        }
        
        isTrackedPool[pool] = false;
        emit PoolUntracked(pool);
    }

    /**
     * @dev View function to get all tracked pools
     */
    function getTrackedPools() external view returns (address[] memory) {
        return trackedPools;
    }

    /**
     * @dev Starts price discovery period for a new token
     */
    function startPriceDiscovery(
        address token,
        uint256 initialSubscribers
    ) external onlyOwner {
        require(!priceDiscovery[token].isInDiscovery, "Already in discovery");
        
        priceDiscovery[token] = PriceDiscoveryData({
            initialSubscribers: initialSubscribers,
            currentSubscribers: initialSubscribers,
            observationStartTime: block.timestamp,
            isInDiscovery: true,
            engagementSnapshots: new uint256[](0)
        });
        
        emit PriceDiscoveryStarted(token, initialSubscribers);
    }

    /**
     * @dev Records engagement snapshot during discovery period
     */
    function recordEngagementSnapshot(
        address token,
        uint256 currentSubscribers
    ) external onlyOwner {
        PriceDiscoveryData storage data = priceDiscovery[token];
        require(data.isInDiscovery, "Not in discovery");
        require(
            data.engagementSnapshots.length < MAX_SNAPSHOTS,
            "Max snapshots reached"
        );
        
        data.engagementSnapshots.push(currentSubscribers);
        data.currentSubscribers = currentSubscribers;
        
        emit EngagementSnapshotTaken(token, currentSubscribers, block.timestamp);
        
        // If we have enough snapshots, complete discovery
        if (data.engagementSnapshots.length == MAX_SNAPSHOTS) {
            completePriceDiscovery(token);
        }
    }

    /**
     * @dev Completes price discovery and sets initial liquidity
     */
    function completePriceDiscovery(address token) public onlyOwner {
        PriceDiscoveryData storage data = priceDiscovery[token];
        require(data.isInDiscovery, "Not in discovery");
        require(
            block.timestamp >= data.observationStartTime + DISCOVERY_PERIOD,
            "Discovery period not complete"
        );
        
        // Calculate optimal initial price based on engagement trends
        uint256 initialPrice = calculateInitialPrice(token);
        uint256 initialLiquidity = calculateInitialLiquidity(
            data.currentSubscribers,
            initialPrice
        );
        
        // Set initial liquidity
        sonicReserves[token] = initialLiquidity;
        
        // Mark discovery as complete
        data.isInDiscovery = false;
        
        emit PriceDiscoveryCompleted(token, initialPrice, initialLiquidity);
    }

    /**
     * @dev Calculates initial price based on engagement trends with circuit breaker
     */
    function calculateInitialPrice(
        address token
    ) internal returns (uint256) {
        PriceDiscoveryData storage data = priceDiscovery[token];
        
        // Calculate engagement growth rate
        uint256 totalGrowth = 0;
        uint256 positiveSnapshots = 0;
        
        for (uint256 i = 0; i < data.engagementSnapshots.length; i++) {
            if (i > 0) {
                // Handle potential negative growth
                if (data.engagementSnapshots[i] > data.engagementSnapshots[i-1]) {
                    uint256 growth = ((data.engagementSnapshots[i] - data.engagementSnapshots[i-1]) * 10000) 
                                    / data.engagementSnapshots[i-1];
                    
                    // Cap individual growth rate
                    growth = growth > 10000 ? 10000 : growth; // Cap at 100% per snapshot
                    
                    totalGrowth += growth;
                    positiveSnapshots++;
                }
            }
        }
        
        // Calculate average growth, defaulting to 0 if no positive growth
        uint256 avgGrowth = positiveSnapshots > 0 ? 
            totalGrowth / positiveSnapshots : 
            0;
        
        // Calculate price with growth rate
        uint256 calculatedPrice = BASE_PRICE + ((BASE_PRICE * avgGrowth) / 10000);
        
        // Calculate maximum allowed price
        uint256 maxAllowedPrice = BASE_PRICE * MAX_PRICE_MULTIPLIER;
        
        // Apply circuit breaker logic
        if (calculatedPrice > maxAllowedPrice) {
            emit PriceCapReached(token, calculatedPrice, maxAllowedPrice);
            calculatedPrice = maxAllowedPrice;
        }
        
        return calculatedPrice > MIN_TOKEN_PRICE ? calculatedPrice : MIN_TOKEN_PRICE;
    }

    // Add event for monitoring price caps
    event PriceCapReached(
        address indexed token,
        uint256 calculatedPrice,
        uint256 cappedPrice
    );

    // Add event for fallback pool selection
    event FallbackPoolSelected(
        address indexed pool,
        uint256 reserveAmount
    );

    // Add event for skipped rebalances
    event RebalanceSkipped(
        address indexed token,
        uint256 currentLiquidity,
        string reason
    );

    // Enhanced pool selection event
    event PoolSelected(
        address indexed pool,
        uint256 score,
        uint256 currentLiquidity
    );

    /**
     * @dev Updates pool trading activity
     */
    function updatePoolActivity(
        address pool,
        uint256 tradeAmount
    ) external onlyDEX {
        PoolActivity storage activity = poolActivity[pool];
        
        // Update last trade timestamp
        activity.lastTradeTimestamp = block.timestamp;
        
        // Update 24h volume
        if (block.timestamp >= activity.lastVolumeUpdateTime + 24 hours) {
            // Reset volume after 24h
            activity.tradingVolume24h = tradeAmount;
            activity.lastVolumeUpdateTime = block.timestamp;
        } else {
            activity.tradingVolume24h += tradeAmount;
        }
        
        emit PoolActivityUpdated(
            pool,
            activity.tradingVolume24h,
            block.timestamp
        );
    }

    /**
     * @dev Checks if pool is actively traded
     */
    function isPoolActive(address pool) public view returns (bool) {
        // Cache struct to save gas
        PoolActivity storage activity = poolActivity[pool];
        uint256 lastTrade = activity.lastTradeTimestamp;
        uint256 volume = activity.tradingVolume24h;
        
        // Short circuit evaluation saves gas
        if (lastTrade < block.timestamp - ACTIVITY_THRESHOLD) return false;
        if (volume < MIN_24H_VOLUME) return false;
        return true;
    }

    /**
     * @dev Calculates pool score based on liquidity and activity
     */
    function calculatePoolScore(
        address pool
    ) internal view returns (uint256) {
        // Cache values to save gas
        uint256 liquidity = sonicReserves[pool];
        uint256 volume = poolActivity[pool].tradingVolume24h;
        
        // Use bit shifts for multiplication (more gas efficient)
        // 70% = multiply by 7/10
        // 30% = multiply by 3/10
        return (
            (liquidity * 7 + volume * 3) / 10
        );
    }

    /**
     * @dev View function to get pool activity metrics
     */
    function getPoolActivity(
        address pool
    ) external view returns (
        uint256 lastTrade,
        uint256 volume24h,
        bool isActive
    ) {
        PoolActivity storage activity = poolActivity[pool];
        return (
            activity.lastTradeTimestamp,
            activity.tradingVolume24h,
            isPoolActive(pool)
        );
    }

    // Add function to set DEX address
    function setDEX(address _dex) external onlyOwner {
        require(_dex != address(0), "Invalid DEX address");
        dex = _dex;
    }

    // Move calculateInitialLiquidity before it's used
    function calculateInitialLiquidity(
        uint256 subscriberCount,
        uint256 price
    ) public pure returns (uint256) {
        return subscriberCount * price;
    }

    // Update onlyDEX modifier to use role
    modifier onlyDEX() {
        require(hasRole(DEX_ROLE, msg.sender), "Caller is not DEX");
        _;
    }

    // Add function to grant DEX role (only admin)
    function grantDEXRole(address dex) external onlyOwner {
        grantRole(DEX_ROLE, dex);
    }

    // Add function to revoke DEX role (only admin)
    function revokeDEXRole(address dex) external onlyOwner {
        revokeRole(DEX_ROLE, dex);
    }

    // Add receive function to accept native S token
    receive() external payable {}

    // Add fallback function to accept native S token with data
    fallback() external payable {}
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):