BaseBase
0x69...b122
Kamala Haggis

Kamala Haggis

HAG

代币
市值
$1.00
 
价格
2%
此合同的源代码已经过验证!
合同元数据
编译器
0.8.20+commit.a1b79de6
语言
Solidity
合同源代码
文件 1 的 12:IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @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 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);
}
合同源代码
文件 2 的 12:IG8keepDeployerVesting.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;

interface IG8keepDeployerVesting {
    function deploymentVest(address _deployer, uint256 _tokensToDeployer, uint256 _vestTime)
        external
        returns (uint256 vestingId);
}
合同源代码
文件 3 的 12:IG8keepFactory.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;

interface IG8keepFactory {
    function g8keepTokenVesting() external view returns (address);
}
合同源代码
文件 4 的 12:IUniswapV2Factory.sol
pragma solidity ^0.8.20;

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}
合同源代码
文件 5 的 12:IUniswapV2Pair.sol
pragma solidity ^0.8.20;

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
    function mint(address to) external returns (uint256 liquidity);
    function burn(address to) external returns (uint256 amount0, uint256 amount1);
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
    function sync() external;
    function skim(address to) external;
}
合同源代码
文件 6 的 12:IUniswapV2Router02.sol
pragma solidity ^0.8.20;

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

    function WETH() external pure returns (address);

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

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

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

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

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
}
合同源代码
文件 7 的 12:Ownable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to call the function.
    error Unauthorized();

    /// @dev The `newOwner` cannot be the zero address.
    error NewOwnerIsZeroAddress();

    /// @dev The `pendingOwner` does not have a valid handover request.
    error NoHandoverRequest();

    /// @dev Cannot double-initialize.
    error AlreadyInitialized();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
    /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
    /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
    /// despite it not being as lightweight as a single argument event.
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    /// @dev An ownership handover to `pendingOwner` has been requested.
    event OwnershipHandoverRequested(address indexed pendingOwner);

    /// @dev The ownership handover to `pendingOwner` has been canceled.
    event OwnershipHandoverCanceled(address indexed pendingOwner);

    /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
    uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
        0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;

    /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
        0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;

    /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
        0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The owner slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _OWNER_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;

    /// The ownership handover slot of `newOwner` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
    ///     let handoverSlot := keccak256(0x00, 0x20)
    /// ```
    /// It stores the expiry timestamp of the two-step ownership handover.
    uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
    function _guardInitializeOwner() internal pure virtual returns (bool guard) {}

    /// @dev Initializes the owner directly without authorization guard.
    /// This function must be called upon initialization,
    /// regardless of whether the contract is upgradeable or not.
    /// This is to enable generalization to both regular and upgradeable contracts,
    /// and to save gas in case the initial owner is not the caller.
    /// For performance reasons, this function will not check if there
    /// is an existing owner.
    function _initializeOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                if sload(ownerSlot) {
                    mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                    revert(0x1c, 0x04)
                }
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(_OWNER_SLOT, newOwner)
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        }
    }

    /// @dev Sets the owner directly without authorization guard.
    function _setOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, newOwner)
            }
        }
    }

    /// @dev Throws if the sender is not the owner.
    function _checkOwner() internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner, revert.
            if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns how long a two-step ownership handover is valid for in seconds.
    /// Override to return a different value if needed.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
        return 48 * 3600;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to transfer the ownership to `newOwner`.
    function transferOwnership(address newOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(shl(96, newOwner)) {
                mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                revert(0x1c, 0x04)
            }
        }
        _setOwner(newOwner);
    }

    /// @dev Allows the owner to renounce their ownership.
    function renounceOwnership() public payable virtual onlyOwner {
        _setOwner(address(0));
    }

    /// @dev Request a two-step ownership handover to the caller.
    /// The request will automatically expire in 48 hours (172800 seconds) by default.
    function requestOwnershipHandover() public payable virtual {
        unchecked {
            uint256 expires = block.timestamp + _ownershipHandoverValidFor();
            /// @solidity memory-safe-assembly
            assembly {
                // Compute and set the handover slot to `expires`.
                mstore(0x0c, _HANDOVER_SLOT_SEED)
                mstore(0x00, caller())
                sstore(keccak256(0x0c, 0x20), expires)
                // Emit the {OwnershipHandoverRequested} event.
                log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
            }
        }
    }

    /// @dev Cancels the two-step ownership handover to the caller, if any.
    function cancelOwnershipHandover() public payable virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x20), 0)
            // Emit the {OwnershipHandoverCanceled} event.
            log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
        }
    }

    /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
    /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
    function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            let handoverSlot := keccak256(0x0c, 0x20)
            // If the handover does not exist, or has expired.
            if gt(timestamp(), sload(handoverSlot)) {
                mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                revert(0x1c, 0x04)
            }
            // Set the handover slot to 0.
            sstore(handoverSlot, 0)
        }
        _setOwner(pendingOwner);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the owner of the contract.
    function owner() public view virtual returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_OWNER_SLOT)
        }
    }

    /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
    function ownershipHandoverExpiresAt(address pendingOwner)
        public
        view
        virtual
        returns (uint256 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the handover slot.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            // Load the handover slot.
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         MODIFIERS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Marks a function as only callable by the owner.
    modifier onlyOwner() virtual {
        _checkOwner();
        _;
    }
}
合同源代码
文件 8 的 12:SafeTransferLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
///   responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /// @dev The Permit2 operation has failed.
    error Permit2Failed();

    /// @dev The Permit2 amount must be less than `2**160 - 1`.
    error Permit2AmountOverflow();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /// @dev The unique EIP-712 domain domain separator for the DAI token contract.
    bytes32 internal constant DAI_DOMAIN_SEPARATOR =
        0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;

    /// @dev The address for the WETH9 contract on Ethereum mainnet.
    address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    /// @dev The canonical Permit2 address.
    /// [Github](https://github.com/Uniswap/permit2)
    /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
    address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function trySafeTransferFrom(address token, address from, address to, uint256 amount)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            success :=
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x34, 0) // Store 0 for the `amount`.
                mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                mstore(0x34, amount) // Store back the original `amount`.
                // Retry the approval, reverting upon failure.
                if iszero(
                    and(
                        or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                        call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    )
                ) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul( // The arguments of `mul` are evaluated from right to left.
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// If the initial attempt fails, try to use Permit2 to transfer the token.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
        if (!trySafeTransferFrom(token, from, to, amount)) {
            permit2TransferFrom(token, from, to, amount);
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
    /// Reverts upon failure.
    function permit2TransferFrom(address token, address from, address to, uint256 amount)
        internal
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(m, 0x74), shr(96, shl(96, token)))
            mstore(add(m, 0x54), amount)
            mstore(add(m, 0x34), to)
            mstore(add(m, 0x20), shl(96, from))
            // `transferFrom(address,address,uint160,address)`.
            mstore(m, 0x36c78516000000000000000000000000)
            let p := PERMIT2
            let exists := eq(chainid(), 1)
            if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
            if iszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
                mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
            }
        }
    }

    /// @dev Permit a user to spend a given amount of
    /// another user's tokens via native EIP-2612 permit if possible, falling
    /// back to Permit2 if native permit fails or is not implemented on the token.
    function permit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        bool success;
        /// @solidity memory-safe-assembly
        assembly {
            for {} shl(96, xor(token, WETH9)) {} {
                mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
                if iszero(
                    and( // The arguments of `and` are evaluated from right to left.
                        lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
                        // Gas stipend to limit gas burn for tokens that don't refund gas when
                        // an non-existing function is called. 5K should be enough for a SLOAD.
                        staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
                    )
                ) { break }
                // After here, we can be sure that token is a contract.
                let m := mload(0x40)
                mstore(add(m, 0x34), spender)
                mstore(add(m, 0x20), shl(96, owner))
                mstore(add(m, 0x74), deadline)
                if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
                    mstore(0x14, owner)
                    mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
                    mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
                    mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
                    // `nonces` is already at `add(m, 0x54)`.
                    // `1` is already stored at `add(m, 0x94)`.
                    mstore(add(m, 0xb4), and(0xff, v))
                    mstore(add(m, 0xd4), r)
                    mstore(add(m, 0xf4), s)
                    success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
                    break
                }
                mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
                mstore(add(m, 0x54), amount)
                mstore(add(m, 0x94), and(0xff, v))
                mstore(add(m, 0xb4), r)
                mstore(add(m, 0xd4), s)
                success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
                break
            }
        }
        if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
    }

    /// @dev Simple permit on the Permit2 contract.
    function simplePermit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(m, 0x927da105) // `allowance(address,address,address)`.
            {
                let addressMask := shr(96, not(0))
                mstore(add(m, 0x20), and(addressMask, owner))
                mstore(add(m, 0x40), and(addressMask, token))
                mstore(add(m, 0x60), and(addressMask, spender))
                mstore(add(m, 0xc0), and(addressMask, spender))
            }
            let p := mul(PERMIT2, iszero(shr(160, amount)))
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
                    staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
                )
            ) {
                mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(p))), 0x04)
            }
            mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
            // `owner` is already `add(m, 0x20)`.
            // `token` is already at `add(m, 0x40)`.
            mstore(add(m, 0x60), amount)
            mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
            // `nonce` is already at `add(m, 0xa0)`.
            // `spender` is already at `add(m, 0xc0)`.
            mstore(add(m, 0xe0), deadline)
            mstore(add(m, 0x100), 0x100) // `signature` offset.
            mstore(add(m, 0x120), 0x41) // `signature` length.
            mstore(add(m, 0x140), r)
            mstore(add(m, 0x160), s)
            mstore(add(m, 0x180), shl(248, v))
            if iszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
                mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
                revert(0x1c, 0x04)
            }
        }
    }
}
合同源代码
文件 9 的 12:g8keepFactory.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {g8keepToken} from "./g8keepToken.sol";
import {g8keepPenaltyReceiver} from "./g8keepPenaltyReceiver.sol";
import {g8keepFactoryConfiguration} from "./g8keepFactoryConfiguration.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";
import {IUniswapV2Pair} from "./interfaces/IUniswapV2Pair.sol";
import {IG8keepFactory} from "./interfaces/IG8keepFactory.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

/**
 * @title   g8keepFactory
 * @notice  Factory for managing deployment parameters, deploying g8keep
 * @notice  token contracts, locking initial liquidity, and managing token fees.
 */
contract g8keepFactory is IG8keepFactory, Ownable {

    /// @dev Wrapped native token contract for token deployments using native currency.
    address public immutable WETH;
    /// @dev Uniswap V2 router address for the chain.
    address public immutable UNISWAP_V2_ROUTER;
    /// @dev Vesting contract to be used when deployer tokens are subject to vesting restrictions.
    address public g8keepTokenVesting;
    /// @dev Fee wallet to receive initial liquidity fees.
    address public g8keepFeeWallet;
    /// @dev Address that will receive penalty tax tokens.
    address public penaltyReceiver;
    /// @dev Fee in BPS assessed on token trades, paid to g8keep. Initial value: 1.0%
    uint16 public g8keepFee = 100;
    /// @dev Fee in BPS assessed on initial liquidity supplied. Initial value: 2.0%
    uint16 public g8keepInitialLiquidityFee = 200;
    /// @dev Maximum buy fee in BPS assessed on token trades, paid to token deployer. Initial value: 5.0%
    uint16 public maxBuyFee = 500;
    /// @dev Maximum sell fee in BPS assessed on token trades, paid to token deployer. Initial value: 5.0%
    uint16 public maxSellFee = 500;
    /// @dev Minimum time in seconds that a deployer's share of tokens must be vested over. Initial value: 1 day
    uint24 public minimumDeployVestTime = 1 days;
    /// @dev Minimum time in seconds that a deployed token must have snipe protection enabled.
    uint24 public minimumSnipeProtectionSeconds = 30 minutes;
    /// @dev Maximum time in seconds that a deployed token may have snipe protection enabled.
    uint24 public maximumSnipeProtectionSeconds = 30 days;
    /// @dev Minimum time in seconds that a deployed token must have heavy snipe protection enabled.
    uint24 public minimumHeavySnipeProtectionSeconds = 30 minutes;
    /// @dev Maximum time in seconds that a deployed token may have heavy snipe protection enabled.
    uint24 public maximumHeavySnipeProtectionSeconds = 1 days;
    /// @dev Minimum exponent that a deployed token must have for heavy snipe penalties.
    uint8 public minimumHeavySnipeExponent = 20;
    /// @dev Maximum exponent that a deployed token may have for heavy snipe penalties.
    uint8 public maximumHeavySnipeExponent = 60;

    /// @dev Guardrail for `g8keepFee` to prevent it from being set over 100 BPS.
    uint16 private constant MAX_SETTING_G8KEEP_FEE = 100;
    /// @dev Guardrail for `g8keepInitialLiquidityFee` to prevent it from being set over 200 BPS.
    uint16 private constant MAX_SETTING_G8KEEP_INITIAL_LIQUIDITY_FEE = 200;
    /// @dev Guardrail for `maxBuyFee` to prevent it from being set over 500 BPS.
    uint16 private constant MAX_SETTING_BUY_FEE = 500;
    /// @dev Guardrail for `maxSellFee` to prevent it from being set over 500 BPS.
    uint16 private constant MAX_SETTING_SELL_FEE = 500;
    /// @dev Guardrail for `minimumDeployVestTime` to prevent it from being set over 90 days.
    uint40 private constant MAX_SETTING_MINIMUM_DEPLOY_VEST_TIME = 90 days;
    /// @dev Guardrail for `minimumSnipeProtectionSeconds` to prevent it from being set over 7 days.
    uint40 private constant MAX_SETTING_MINIMUM_SNIPE_PROTECTION_SECONDS = 7 days;
    /// @dev Guardrail for `maximumSnipeProtectionSeconds` to prevent it from being set over 365 days.
    uint40 private constant MAX_SETTING_MAXIMUM_SNIPE_PROTECTION_SECONDS = 365 days;
    /// @dev Guardrail for `minimumHeavySnipeProtectionSeconds` to prevent it from being set over 1 days.
    uint40 private constant MAX_SETTING_MINIMUM_HEAVY_SNIPE_PROTECTION_SECONDS = 1 days;
    /// @dev Guardrail for `maximumHeavySnipeProtectionSeconds` to prevent it from being set over 5 days.
    uint40 private constant MAX_SETTING_MAXIMUM_HEAVY_SNIPE_PROTECTION_SECONDS = 5 days;
    /// @dev Guardrail for `minimumHeavySnipeExponent` to prevent it from being set under 2.
    uint8 private constant MIN_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT = 2;
    /// @dev Guardrail for `minimumHeavySnipeExponent` to prevent it from being set over 20.
    uint8 private constant MAX_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT = 20;
    /// @dev Guardrail for `maximumHeavySnipeExponent` to prevent it from being set over 100.
    uint8 private constant MAX_SETTING_MAXIMUM_HEAVY_SNIPE_EXPONENT = 100;
    /// @dev Constant value for BPS.
    uint16 private constant BPS = 10_000;

    /// @dev Mapping to flag a token address as an allowed pair token for g8keep token deployments.
    mapping(address => bool) public allowedPairs;
    /// @dev Mapping of the minimum initial liquidity to be provided for a paired token.
    mapping(address => uint256) public pairedTokenMinimumLiquidity;
    /// @dev Mapping of tokens that are locked to prevent sale or withdrawal.
    mapping(address => bool) private lockedTokens;

    /// @dev Emitted when a token is deployed.
    event TokenDeployed(
        address indexed token,
        address indexed pair,
        address indexed deployer,
        string symbol
    );

    /// @dev Emitted when the g8keepFactory owner updates deployment settings.
    event DeploymentSettingsUpdated(
        uint16 maxBuyFee,
        uint16 maxSellFee,
        uint24 minimumDeployVestTime,
        uint24 minimumSnipeProtectionSeconds,
        uint24 maximumSnipeProtectionSeconds,
        uint24 minimumHeavySnipeProtectionSeconds,
        uint24 maximumHeavySnipeProtectionSeconds,
        uint8 minimumHeavySnipeExponent,
        uint8 maximumHeavySnipeExponent
    );

    /// @dev Emitted when the g8keepFactory owner updates a paired token settings.
    event PairedTokenSettingsUpdated(
        address indexed pairedToken,
        bool allowed,
        uint256 minimumLiquidity
    );

    /// @dev Emitted when the g8keepFactory owner updates a penalty receiver.
    event PenaltyReceiverUpdated(
        address indexed penaltyReceiver
    );

    /// @dev Thrown when arrays must match in length and they do not.
    error ArrayLengthMismatch();
    /// @dev Thrown when selling tokens and the deadline for the sale has passed.
    error DeadlinePassed();
    /// @dev Thrown when the deposit to the WETH contract for initial liquidity fails.
    error FailedToDepositWETH();
    /// @dev Thrown when deploying a token and the parameters are not compliant with deployment settings.
    error InvalidDeploymentParameters();
    /// @dev Thrown when the g8keepFactory owner is updating deployment settings and they are not compliant
    /// @dev with the constant guardrails or when setting a penalty receiver that is the current receiver.
    error InvalidSettings();
    /// @dev Thrown when deploying a token and the initial liquidity is less than the minimum required.
    error NotEnoughInitialLiquidity();
    /// @dev Thrown when deploying a token with a paired token that is not allowed.
    error PairNotAllowed();
    /// @dev Thrown when attempting to withdraw a token that is locked.
    error TokenLocked();
    /// @dev Thrown when native value is sent with a token deployment that does not use wrapped native.
    error ValueNotAllowedForNonWETHPairs();
    /// @dev Thrown when deploying a token and the value sent exceeds the initial liquidity specified.
    error ValueSentNotValid();
    /// @dev Thrown when withdrawing native token from the contract and the withdrawal fails.
    error WithdrawalFailed();
    /// @dev Thrown when a supplied address that must be non-zero is zero.
    error ZeroAddress();

    /// @dev Struct used for defining token sale parameters.
    struct TokenSale {
        address tokenAddress; // Address of the token to sell.
        uint256 amountToSell; // Amount of the token to sell.
        uint256 amountOutMin; // Minimum amount of paired token to receive.
        address recipient;    // Address to receive the sale value at.
    }

    /**
     * @dev Constructs the g8keepFactory contract.
     * @dev Initialization parameters are retrieved from a configuration contract so that
     * @dev the g8keepFactory may be deterministically deployed on EVM chains at the same
     * @dev address. 
     * 
     * @param _g8keepFactoryConfiguration  The address of the factory configuration contract to retrieve configuration parameters.
     */
    constructor(address _g8keepFactoryConfiguration) {
        g8keepFactoryConfiguration config = g8keepFactoryConfiguration(_g8keepFactoryConfiguration);
        (
            address _defaultOwner,
            address _g8keepFeeWallet,
            address _uniswapV2Router,
            address _WETH,
            uint256 _minimumWETHLiquidity
        ) = config.getFactoryConfiguration();

        if (
            _defaultOwner == address(0)
            || _g8keepFeeWallet == address(0)
            || _uniswapV2Router == address(0)
            || _WETH == address(0)
        ) {
            revert ZeroAddress();
        }

        _initializeOwner(_defaultOwner);
        allowedPairs[_WETH] = true;
        pairedTokenMinimumLiquidity[_WETH] = _minimumWETHLiquidity;
        WETH = _WETH;
        UNISWAP_V2_ROUTER = _uniswapV2Router;
        g8keepFeeWallet = _g8keepFeeWallet;
        
        penaltyReceiver = address(new g8keepPenaltyReceiver(_defaultOwner));

        SafeTransferLib.safeApprove(_WETH, _uniswapV2Router, type(uint256).max);
    }

    /////////// PUBLIC FUNCTIONS ///////////

    /**
     * @notice Deploys a g8keepToken contract, creates and funds the LP, locks initial liquidity.
     *
     * @dev    Parameters must be within ranges defined by the factory settings.
     * @dev    Token salt value must be calculated to generate a token deployment address
     * @dev    that is greater than the `_pairedToken` address.
     *
     * @param _initialLiquidity        Amount of paired token to supply as liquidity in the LP.
     * @param _name                    Name of the token being deployed.
     * @param _symbol                  Symbol of the token being deployed.
     * @param _totalSupply             Total supply to be minted during deployment.
     * @param _treasuryWallet          Address that will receive deployer buy and sell fees.
     * @param _buyFee                  Buy fee in BPS.
     * @param _sellFee                 Sell fee in BPS.
     * @param _pairedToken             Address of the token to pair with in the LP.
     * @param _deployReserve           Amount of total supply in BPS to allocate to deployer.
     * @param _deployVestTime          Time in seconds to vest the deployer's allocation.
     * @param _snipeProtectionSeconds  Amount of time in seconds that snipe protection will be enabled.
     * @param _heavySnipeSeconds       Amount of time in seconds that heavy snipe penalty is enabled.
     * @param _heavySnipeExponent      Starting exponent for heavy snipe penalty.
     * @param _tokenSalt               Salt for token deployment to generate a deterministic address.
     */
    function deployToken(
        uint256 _initialLiquidity,
        string memory _name,
        string memory _symbol,
        uint256 _totalSupply,
        address _treasuryWallet,
        uint16 _buyFee,
        uint16 _sellFee,
        address _pairedToken,
        uint256 _deployReserve,
        uint256 _deployVestTime,
        uint256 _snipeProtectionSeconds,
        uint256 _heavySnipeSeconds,
        uint256 _heavySnipeExponent,
        bytes32 _tokenSalt
    ) external payable returns (address _tokenAddress) {
        if (!allowedPairs[_pairedToken]) revert PairNotAllowed();
        if (
            _buyFee > maxBuyFee || _sellFee > maxSellFee || _deployVestTime < minimumDeployVestTime
                || (_deployVestTime > 0 && g8keepTokenVesting == address(0))
                || _snipeProtectionSeconds < minimumSnipeProtectionSeconds
                || _snipeProtectionSeconds > maximumSnipeProtectionSeconds 
                || _heavySnipeSeconds < minimumHeavySnipeProtectionSeconds
                || _heavySnipeSeconds > maximumHeavySnipeProtectionSeconds 
                || _heavySnipeExponent < minimumHeavySnipeExponent
                || _heavySnipeExponent > maximumHeavySnipeExponent
                || lockedTokens[_pairedToken]
                || _treasuryWallet == address(0)
        ) {
            revert InvalidDeploymentParameters();
        }

        if (_pairedToken == WETH) {
            if (msg.value > _initialLiquidity) {
                revert ValueSentNotValid();
            }

            // Deposit native tokens to WETH
            if (msg.value > 0) {
                (bool success,) = WETH.call{value: msg.value}("");
                if (!success) revert FailedToDepositWETH();
            }
            // If value is insufficient for liquidity, attempt to transfer WETH
            if (msg.value < _initialLiquidity) {
                unchecked {
                    SafeTransferLib.safeTransferFrom(
                        WETH,
                        msg.sender,
                        address(this),
                        _initialLiquidity - msg.value
                    );
                }
            }
        } else if (msg.value > 0) {
            revert ValueNotAllowedForNonWETHPairs();
        } else {
            uint256 balanceBefore = IERC20(_pairedToken).balanceOf(address(this));
            SafeTransferLib.safeTransferFrom(_pairedToken, msg.sender, address(this), _initialLiquidity);
            uint256 balanceAfter = IERC20(_pairedToken).balanceOf(address(this));
            if (balanceAfter > balanceBefore) {
                unchecked {
                    _initialLiquidity = balanceAfter - balanceBefore;
                }
            } else {
                _initialLiquidity = 0;
            }
        }

        g8keepToken token = new g8keepToken{salt: _tokenSalt}(
            msg.sender,
            _name,
            _symbol,
            _totalSupply,
            _treasuryWallet,
            penaltyReceiver,
            _buyFee,
            _sellFee,
            g8keepFee,
            UNISWAP_V2_ROUTER,
            _pairedToken,
            _deployReserve,
            _deployVestTime,
            _snipeProtectionSeconds,
            _heavySnipeSeconds,
            _heavySnipeExponent
        );

        address pairAddress = token.UNISWAP_V2_PAIR();

        if (IERC20(_pairedToken).balanceOf(pairAddress) > 0) {
            uint256 balanceBefore = IERC20(_pairedToken).balanceOf(address(this));
            IUniswapV2Pair(pairAddress).skim(address(this));
            uint256 balanceAfter = IERC20(_pairedToken).balanceOf(address(this));
            if (balanceAfter > balanceBefore) {
                unchecked {
                    _initialLiquidity = _initialLiquidity + balanceAfter - balanceBefore;
                }
            }
        }

        if (_initialLiquidity < pairedTokenMinimumLiquidity[_pairedToken]) revert NotEnoughInitialLiquidity();

        //Prevent sales of the LP token
        lockedTokens[pairAddress] = true;

        uint256 valueToG8keep = (_initialLiquidity * g8keepInitialLiquidityFee) / BPS;
        if (valueToG8keep > 0) {
            SafeTransferLib.safeTransfer(_pairedToken, g8keepFeeWallet, valueToG8keep);
        }


        // add liquidity to LP
        IUniswapV2Router02(UNISWAP_V2_ROUTER).addLiquidity(
            _pairedToken,
            address(token),
            _initialLiquidity - valueToG8keep,
            token.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp
        );

        // emit Event for UI to store token details
        emit TokenDeployed(address(token), pairAddress, msg.sender, _symbol);

        return address(token);
    }

    /////////// OWNER FUNCTIONS ///////////

    /**
     * @notice Admin function to configure a paired token.
     *
     * @param pairedToken       Address of the paired token to configure.
     * @param allowed           True if the token is allowed as a paired token, false if not.
     * @param minimumLiquidity  Minimum amount of token that may be supplied as initial liquidity.
     */
    function setPairedTokenSettings(
        address pairedToken,
        bool allowed,
        uint256 minimumLiquidity
    ) external onlyOwner {
        uint256 allowanceAmount;
        if (allowed) {
            allowanceAmount = type(uint256).max;
        }

        _setPairedTokenSettings(pairedToken, allowed, minimumLiquidity, allowanceAmount);
    }

    /**
     * @notice Admin function to configure a paired token with a specific allowance amount.
     *
     * @dev    Used for certain ERC20 tokens that have unusual approval methods.
     *
     * @param pairedToken       Address of the paired token to configure.
     * @param allowed           True if the token is allowed as a paired token, false if not.
     * @param minimumLiquidity  Minimum amount of token that may be supplied as initial liquidity.
     * @param allowanceAmount   Amount to set for the allowance to the Uniswap Router.
     */
    function setPairedTokenSettingsWithAllowance(
        address pairedToken,
        bool allowed,
        uint256 minimumLiquidity,
        uint256 allowanceAmount
    ) external onlyOwner {
        _setPairedTokenSettings(pairedToken, allowed, minimumLiquidity, allowanceAmount);
    }

    /**
     * @notice  Admin function to configure deployment parameters.
     *
     * @dev     Settings parameters must be within the constant guardrails defined.
     * 
     * @param _maxBuyFee                           Maximum buy fee in BPS.
     * @param _maxSellFee                          Maximum sell fee in BPS.
     * @param _minimumDeployVestTime               Minimum time in seconds that a deployer tokens must vest over.
     * @param _minimumSnipeProtectionSeconds       Minimum time in seconds for snipe protection to be enabled.
     * @param _maximumSnipeProtectionSeconds       Maximum time in seconds for snipe protection to be enabled.
     * @param _minimumHeavySnipeProtectionSeconds  Minimum time in seconds for heavy snipe protection to be enabled.
     * @param _maximumHeavySnipeProtectionSeconds  Maximum time in seconds for heavy snipe protection to be enabled.
     * @param _minimumHeavySnipeExponent           Minimum exponent penalty during heavy snipe protection.
     * @param _maximumHeavySnipeExponent           Maximum exponent penalty during heavy snipe protection.
     */
    function setDeploymentSettings(
        uint16 _maxBuyFee,
        uint16 _maxSellFee,
        uint24 _minimumDeployVestTime,
        uint24 _minimumSnipeProtectionSeconds,
        uint24 _maximumSnipeProtectionSeconds,
        uint24 _minimumHeavySnipeProtectionSeconds,
        uint24 _maximumHeavySnipeProtectionSeconds,
        uint8 _minimumHeavySnipeExponent,
        uint8 _maximumHeavySnipeExponent
    ) external onlyOwner {
        if (
            _maxBuyFee > MAX_SETTING_BUY_FEE || _maxSellFee > MAX_SETTING_SELL_FEE
                || _minimumDeployVestTime > MAX_SETTING_MINIMUM_DEPLOY_VEST_TIME
                || (_minimumDeployVestTime > 0 && g8keepTokenVesting == address(0))
                || _minimumSnipeProtectionSeconds > MAX_SETTING_MINIMUM_SNIPE_PROTECTION_SECONDS
                || _maximumSnipeProtectionSeconds > MAX_SETTING_MAXIMUM_SNIPE_PROTECTION_SECONDS
                || _minimumSnipeProtectionSeconds > _maximumSnipeProtectionSeconds
                || _minimumHeavySnipeProtectionSeconds > MAX_SETTING_MINIMUM_HEAVY_SNIPE_PROTECTION_SECONDS
                || _maximumHeavySnipeProtectionSeconds > MAX_SETTING_MAXIMUM_HEAVY_SNIPE_PROTECTION_SECONDS
                || _minimumHeavySnipeProtectionSeconds > _maximumHeavySnipeProtectionSeconds
                || _minimumHeavySnipeExponent < MIN_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT
                || _minimumHeavySnipeExponent > MAX_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT
                || _maximumHeavySnipeExponent > MAX_SETTING_MAXIMUM_HEAVY_SNIPE_EXPONENT
                || _minimumHeavySnipeExponent > _maximumHeavySnipeExponent
        ) {
            revert InvalidSettings();
        }

        maxBuyFee = _maxBuyFee;
        maxSellFee = _maxSellFee;
        minimumDeployVestTime = _minimumDeployVestTime;
        minimumSnipeProtectionSeconds = _minimumSnipeProtectionSeconds;
        maximumSnipeProtectionSeconds = _maximumSnipeProtectionSeconds;
        minimumHeavySnipeProtectionSeconds = _minimumHeavySnipeProtectionSeconds;
        maximumHeavySnipeProtectionSeconds = _maximumHeavySnipeProtectionSeconds;
        minimumHeavySnipeExponent = _minimumHeavySnipeExponent;
        maximumHeavySnipeExponent = _maximumHeavySnipeExponent;

        emit DeploymentSettingsUpdated(
            _maxBuyFee,
            _maxSellFee,
            _minimumDeployVestTime,
            _minimumSnipeProtectionSeconds,
            _maximumSnipeProtectionSeconds,
            _minimumHeavySnipeProtectionSeconds,
            _maximumHeavySnipeProtectionSeconds,
            _minimumHeavySnipeExponent,
            _maximumHeavySnipeExponent
        );
    }

    /**
     * @notice  Admin function to set the g8keep fee that will be assessed on token trades.
     *
     * @dev     Fee must be less than the constant guardrail settings.
     *
     * @param _g8keepFee  Fee in BPS to assess on token trades.
     */
    function setG8keepFee(uint16 _g8keepFee) external onlyOwner {
        if (_g8keepFee > MAX_SETTING_G8KEEP_FEE) {
            revert InvalidSettings();
        }

        g8keepFee = _g8keepFee;
    }

    /**
     * @notice  Admin function to set the g8keep fee that will be assessed on initial liquidity.
     *
     * @dev     Fee must be less than the constant guardrail settings.
     *
     * @param _g8keepInitialLiquidityFee  Fee in BPS to assess on initial liquidity.
     */
    function setG8keepInitialLiquidityFee(uint16 _g8keepInitialLiquidityFee) external onlyOwner {
        if (_g8keepInitialLiquidityFee > MAX_SETTING_G8KEEP_INITIAL_LIQUIDITY_FEE) {
            revert InvalidSettings();
        }

        g8keepInitialLiquidityFee = _g8keepInitialLiquidityFee;
    }

    /**
     * @notice  Admin function to set the g8keep vesting contract address.
     *
     * @dev     The vesting contract address may only be set to the zero address when minimum vesting time is set to zero.
     *
     * @param _g8keepTokenVesting  Address of the g8keep token vesting contract.
     */
    function setG8keepTokenVestingAddress(address _g8keepTokenVesting) external onlyOwner {
        if (_g8keepTokenVesting == address(0) && minimumDeployVestTime > 0) {
            revert InvalidSettings();
        }

        g8keepTokenVesting = _g8keepTokenVesting;
    }

    /**
     * @notice  Admin function to set the g8keep fee wallet to receive initial liquidity fees.
     *
     * @dev     The fee wallet cannot be set to the zero address.
     * 
     * @param _g8keepFeeWallet  Address of the g8keep fee wallet.
     */
    function setG8keepFeeWallet(address _g8keepFeeWallet) external onlyOwner {
        if (_g8keepFeeWallet == address(0)) {
            revert ZeroAddress();
        }

        g8keepFeeWallet = _g8keepFeeWallet;
    }

    /**
     * @notice  Admin function to set the penalty receiver address.
     *
     * @dev     The penalty receiver cannot be set to the zero address.
     * 
     * @param _penaltyReceiver  Address of the penalty receiver.
     */
    function setPenaltyReceiver(address _penaltyReceiver) external onlyOwner {
        if (_penaltyReceiver == address(0)) {
            revert ZeroAddress();
        }

        address currentPenaltyReceiver = penaltyReceiver;
        if (currentPenaltyReceiver == _penaltyReceiver) {
            revert InvalidSettings();
        }

        penaltyReceiver = _penaltyReceiver;
        emit PenaltyReceiverUpdated(_penaltyReceiver);
    }

    /**
     * @notice  Admin function to update approvals to the Uniswap router.
     *
     * @dev     Used to update or revoke approvals in case there is an unusual approval
     * @dev     mechanism on a paired ERC20 token.
     * 
     * @param tokenAddresses        Array of tokens to set approvals on.
     * @param approved              Array specifying whether to approve or revoke approval.
     * @param approvalRevokeAmount  Amount to use when revoking approvals.
     */
    function setApprovalToUniswapRouter(
        address[] calldata tokenAddresses,
        bool[] calldata approved,
        uint256 approvalRevokeAmount
    ) external onlyOwner {
        if (tokenAddresses.length != approved.length) {
            revert ArrayLengthMismatch();
        }
        
        for (uint256 i; i < tokenAddresses.length; ++i) {
            address tokenAddress = tokenAddresses[i];
            //skip locked tokens
            if (lockedTokens[tokenAddress]) continue;

            if (approved[i]) {
                SafeTransferLib.safeApproveWithRetry(tokenAddress, UNISWAP_V2_ROUTER, type(uint256).max);
            } else {
                SafeTransferLib.safeApproveWithRetry(tokenAddress, UNISWAP_V2_ROUTER, approvalRevokeAmount);
            }
        }
    }

    /**
     * @notice  Admin function to sell the tokens collected as fees on the g8keep platform.
     * 
     * @dev     Locked tokens supplied in `sellParameters` will be skipped without reverting.
     *
     * @param sellParameters  Array of token sale parameters to execute on the Uniswap router.
     * @param deadline        Timestamp that the transaction must execute by or revert.
     */
    function sellTokens(TokenSale[] calldata sellParameters, uint256 deadline) external onlyOwner {
        if (deadline < block.timestamp) {
            revert DeadlinePassed();
        }
        
        for (uint256 i; i < sellParameters.length; ++i) {
            TokenSale calldata sale = sellParameters[i];
            address tokenAddress = sale.tokenAddress;
            if (lockedTokens[tokenAddress]) {
                // skip locked LP tokens
                continue;
            }
            if (sale.recipient == address(0)) {
                revert ZeroAddress();
            }
            
            address[] memory path = new address[](2);
            path[0] = tokenAddress;
            path[1] = g8keepToken(tokenAddress).PAIRED_TOKEN();
            try IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
                sale.amountToSell, sale.amountOutMin, path, sale.recipient, block.timestamp
            ) {} catch {}
        }
    }

    /**
     * @notice  Admin function to withdraw a token that is held by the factory contract.
     * 
     * @dev     Withdrawing a locked token will revert the transaction.
     * @dev     Will withdraw the entire balance of the token.
     * 
     * @param tokenAddress  Address of the token to withdraw.
     * @param to            Address to withdraw the token to.
     */
    function withdrawToken(address tokenAddress, address to) external onlyOwner {
        if (lockedTokens[tokenAddress]) {
            revert TokenLocked();
        }
        uint256 _contractBalance = IERC20(tokenAddress).balanceOf(address(this));
        SafeTransferLib.safeTransfer(tokenAddress, to, _contractBalance);
    }

    /**
     * @notice  Admin function to withdraw a token that is held by the factory contract.
     * 
     * @dev     Withdrawing a locked token will revert the transaction.
     * @dev     Will withdraw the specified `amount` of token.
     * 
     * @param tokenAddress  Address of the token to withdraw.
     * @param to            Address to withdraw the token to.
     * @param amount        Amount of the token to withdraw.
     */
    function withdrawToken(address tokenAddress, address to, uint256 amount) external onlyOwner {
        if (lockedTokens[tokenAddress]) {
            revert TokenLocked();
        }
        SafeTransferLib.safeTransfer(tokenAddress, to, amount);
    }

    /**
     * @notice  Admin function to withdraw native token that is held by the factory contract.
     * 
     * @dev     Will withdraw the entire balance.
     * 
     * @param to            Address to withdraw the token to.
     */
    function withdrawETH(address to) external onlyOwner {
        if (to == address(0)) revert ZeroAddress();

        (bool success,) = to.call{value: address(this).balance}("");
        if (!success) revert WithdrawalFailed();
    }

    /////////// INTERNAL FUNCTIONS ///////////

    /**
     * @dev  Internal function for `setPairedTokenSettings` and `setPairedTokenSettingsWithAllowance`.
     *
     * @param pairedToken       Address of the paired token to configure.
     * @param allowed           True if the token is allowed as a paired token, false if not.
     * @param minimumLiquidity  Minimum amount of token that may be supplied as initial liquidity.
     * @param allowanceAmount   Amount to set for the allowance to the Uniswap Router.
     */
    function _setPairedTokenSettings(address pairedToken, bool allowed, uint256 minimumLiquidity, uint256 allowanceAmount) internal {
        if (lockedTokens[pairedToken]) revert TokenLocked();

        allowedPairs[pairedToken] = allowed;
        pairedTokenMinimumLiquidity[pairedToken] = minimumLiquidity;
        SafeTransferLib.safeApproveWithRetry(pairedToken, UNISWAP_V2_ROUTER, allowanceAmount);

        emit PairedTokenSettingsUpdated(
            pairedToken,
            allowed,
            minimumLiquidity
        );
    }
}
合同源代码
文件 10 的 12:g8keepFactoryConfiguration.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;

import {Ownable} from "solady/auth/Ownable.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";

/**
 * @title   g8keepFactoryConfiguration
 * @notice  Settings contract for initial deployment of g8keepFactory to allow
 * @notice  for a deterministic address across EVM blockchains where the
 * @notice  initial configuration settings may differ by chain.
 */
contract g8keepFactoryConfiguration is Ownable {
    /// @dev Address that will be the initial owner of the g8keepFactory
    address private g8keepFactoryAdmin;
    /// @dev Address that will be the initial fee recipient of initial liquidity fees.
    address private g8keepFeeWallet;
    /// @dev Address of the Uniswap V2 Router.
    address private uniswapV2Router;
    /// @dev Initial minimum liquidity setting for native tokens.
    uint256 private minimumWETHLiquidity;

    /// @dev Thrown when setting a configuration address to the zero address.
    error ZeroAddress();

    /**
     * @notice  Constructs the g8keepFactoryConfiguration contract with an initial owner.
     * 
     * @param _configurationOwner  Address that owns the configuration contract.
     */
    constructor(address _configurationOwner) {
        _initializeOwner(_configurationOwner);
    }

    /**
     * @notice  Admin function to set the initial configuration parameters for the g8keepFactory.
     * 
     * @param _g8keepFactoryAdmin    Address that will be the initial owner of the g8keepFactory.
     * @param _g8keepFeeWallet       Address that will be the initial recipient of initial liquidity fees.
     * @param _uniswapV2Router       Address of the Uniswap V2 Router.
     * @param _minimumWETHLiquidity  Initial minimum liquidity setting for native tokens.
     */
    function setFactoryConfiguration(
        address _g8keepFactoryAdmin,
        address _g8keepFeeWallet,
        address _uniswapV2Router,
        uint256 _minimumWETHLiquidity
    ) external onlyOwner {
        if (
            _g8keepFactoryAdmin == address(0)
            || _g8keepFeeWallet == address(0)
            || _uniswapV2Router == address(0)
        ) {
            revert ZeroAddress();
        }

        g8keepFactoryAdmin = _g8keepFactoryAdmin;
        g8keepFeeWallet = _g8keepFeeWallet;
        uniswapV2Router = _uniswapV2Router;
        minimumWETHLiquidity = _minimumWETHLiquidity;
    }

    /**
     * @notice  Returns the factory configuration settings in one call for gas efficiency.
     * 
     * @dev     WETH address is retrieved from the Uniswap V2 Router for compliance with ETH<->Token
     * @dev     swaps with native wrapping and unwrapping.
     * 
     * @return _g8keepFactoryAdmin    Address that will be the initial owner of the g8keepFactory.
     * @return _g8keepFeeWallet       Address that will be the initial recipient of initial liquidity fees.
     * @return _uniswapV2Router       Address of the Uniswap V2 Router.
     * @return _WETH                  Address of the WETH contract from Uniswap V2 Router.
     * @return _minimumWETHLiquidity  Initial minimum liquidity setting for native tokens.
     */
    function getFactoryConfiguration() external view returns(
        address _g8keepFactoryAdmin,
        address _g8keepFeeWallet,
        address _uniswapV2Router,
        address _WETH,
        uint256 _minimumWETHLiquidity
    ) {
        _g8keepFactoryAdmin = g8keepFactoryAdmin;
        _g8keepFeeWallet = g8keepFeeWallet;
        _uniswapV2Router = uniswapV2Router;
        _WETH = IUniswapV2Router02(_uniswapV2Router).WETH();
        _minimumWETHLiquidity = minimumWETHLiquidity;
    }
}
合同源代码
文件 11 的 12:g8keepPenaltyReceiver.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;

import {Ownable} from "solady/auth/Ownable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

/**
 * @title   g8keepPenaltyReceiver
 * @notice  Receives tokens from g8keep snipe protection penalties for [redacted].
 */
contract g8keepPenaltyReceiver is Ownable {
    /// @dev Mapping of addresses that are allowed to transfer tokens from the penalty receiver.
    mapping(address => uint256) public allowedTransferers;

    /// @dev Guardrail to prevent a newly added transferer from transfering tokens immediately.
    uint256 private constant ALLOWED_TIME_DELAY = 1 days;

    /// @dev Thrown when allowing or removing a transferer and no change would be made.
    error NoUpdate();
    /// @dev Thrown when a transfer request is made by an address that is not allowed.
    error NotAllowed();
    /// @dev Thrown when withdrawing a token from the contract and the withdrawal fails.
    error WithdrawalFailed();
    /// @dev Thrown when attempting to set the zero address as an allowed transferer.
    error ZeroAddress();
    
    /// @dev Emitted when the penalty receiver owner updates an allowed transferer.
    event AllowedTransfererUpdated(address indexed transferer, bool allowed, uint256 allowedTime);

    modifier onlyAllowedTransferer() {
        uint256 allowedTime = allowedTransferers[msg.sender];
        if (allowedTime == 0 || allowedTime > block.timestamp) {
            revert NotAllowed();
        }
        _;
    }

    /**
     * @dev Constructs the g8keepPenaltyReceiver contract.
     * 
     * @param _defaultOwner  The address of the default owner of the contract.
     */
    constructor(address _defaultOwner) {
        _initializeOwner(_defaultOwner);
    }

    /////////// OWNER FUNCTIONS ///////////

    /**
     * @notice Admin function to configure a paired token.
     *
     * @param transferer  Address of the address to set allowed.
     * @param allowed     True if the address is allowed to transfer tokens, false if not.
     */
    function setAllowedTransferer(
        address transferer,
        bool allowed
    ) external onlyOwner {
        if (transferer == address(0)) {
            revert ZeroAddress();
        }
        uint256 allowedTime = allowedTransferers[transferer];
        if (allowed) {
            if (allowedTime > 0) {
                revert NoUpdate();
            }
            unchecked {
                allowedTime = block.timestamp + ALLOWED_TIME_DELAY;
            }
        } else {
            if (allowedTime == 0) {
                revert NoUpdate();
            }
            allowedTime = 0;
        }
        allowedTransferers[transferer] = allowedTime;

        emit AllowedTransfererUpdated(transferer, allowed, allowedTime);
    }

    /////////// TRANSFERER FUNCTIONS ///////////

    /**
     * @notice  Transfers an `amount` of `tokenAddress` from the contract to `to`.
     * 
     * @dev     Throws when called by an address that is not an allowed transferer.
     * 
     * @param tokenAddress  Address of the token to transfer.
     * @param to            Address to transfer the token to.
     * @param amount        Amount of the token to transfer.
     */
    function transferToken(address tokenAddress, address to, uint256 amount) external onlyAllowedTransferer {
        SafeTransferLib.safeTransfer(tokenAddress, to, amount);
    }
}
合同源代码
文件 12 的 12:g8keepToken.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;

import {Ownable} from "solady/auth/Ownable.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";
import {IUniswapV2Pair} from "./interfaces/IUniswapV2Pair.sol";
import {IG8keepDeployerVesting} from "./interfaces/IG8keepDeployerVesting.sol";
import {IG8keepFactory} from "./interfaces/IG8keepFactory.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

/**
 * @title   g8keepToken
 * @notice  An ERC20 implementation for the g8keep platform.
 * @notice  Key features of g8keepToken include: 
 * @notice  - Fee collection for deployers and guardrails for traders.
 * @notice  - Fees may only be lowered by the deployer, never raised.
 * @notice  - Automated snipe protection with a linear release of tokens 
 * @notice    without snipe penalties and exponential increase of penalty
 * @notice    based on the purchase amount.
 */
contract g8keepToken is Ownable {
    /// @dev Name of the token.
    string public name;
    /// @dev Symbol of the token.
    string public symbol;
    /// @dev Token decimals.
    uint8 public constant decimals = 18;
    /// @dev Total supply of the token.
    uint256 public immutable totalSupply;

    /// @dev g8keep factory address to deposit g8keep fees.
    address private immutable G8KEEP;
    /// @dev Address that will receive penalty taxes.
    address private immutable PENALTY_TAX_RECEIVER;
    /// @dev Address of the Uniswap V2 pair.
    address public immutable UNISWAP_V2_PAIR;
    /// @dev Address of the paired token.
    address public immutable PAIRED_TOKEN;

    /// @dev Constant value for BPS.
    uint16 private constant BPS = 10_000;
    /// @dev Constant value for the maximum deployer reserve of total supply in BPS.
    uint16 private constant MAX_DEPLOYER_RESERVE = 300;
    /// @dev Timestamp of the token deployment - used for calculating snipe protection.
    uint40 private immutable GENESIS_TIME;
    /// @dev The base penalty exponent for snipe protection.
    uint8 private constant SNIPE_PENALTY_BASE_EXPONENT = 2;
    /// @dev Amount of time in seconds that snipe protection is enabled for.
    uint40 public immutable SNIPE_PROTECTION_SECONDS;
    /// @dev Timestamp of when snipe protection for the token ends.
    uint40 public immutable SNIPE_PROTECTION_END;
    /// @dev Starting exponent for heavy snipe penalties.
    uint8 public immutable SNIPE_PROTECTION_HEAVY_EXPONENT_START;
    /// @dev Amount of time in seconds that heavy snipe penalties are enabled for.
    uint40 public immutable SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS;
    /// @dev Fee in BPS assessed by g8keep for trades.
    uint16 public immutable G8KEEP_FEE;

    /// @dev Fee in BPS assessed by the deployer for buys.
    uint16 public buyFee;
    /// @dev Fee in BPS assessed by the deployer for sells.
    uint16 public sellFee;
    /// @dev Address that will receive deployer trading fees.
    address public treasuryWallet;
    /// @dev Mapping of token balances for token owners.
    mapping(address => uint256) private _balances;
    /// @dev Mapping of spender approvals.
    mapping(address => mapping(address => uint256)) private _allowances;

    /// @dev Baseline balance of the LP - used for calculating snipe protection.
    uint256 private lpBalanceBaseline;
    /// @dev Amount of LP token total supply last recorded - used for calculating `thirdPartyLPAmount`.
    uint256 private lastLPTotalSupply;
    /// @dev Amount of token added to the LP by third parties.
    uint112 private thirdPartyLPAmount;
    /// @dev Cached value of the LP token reserve - used for calculating `thirdPartyLPAmount`.
    uint112 private cachedLPReserve1;

    /// @dev Thrown when attempting to burn an LP position during snipe protection.
    error CannotBurnLiquidityDuringSnipeProtection();
    /// @dev Thrown when deployer attempts to set fees higher than the current settings.
    error CannotIncreaseFees();
    /// @dev Thrown when attempting to transfer tokens from and to the Uniswap V2 Pair.
    error CannotTransferFromAndToUniswapV2Pair();
    /// @dev Thrown when the transfer amount on a buy exceeds the value the Uniswap V2 Router calculates.
    error ExcessiveAmountOut();
    /// @dev Thrown when the spender does not have a sufficient allowance for the transfer being made.
    error InsufficientAllowance();
    /// @dev Thrown when a transfer amount exceeds the sender's balance.
    error InsufficientBalance();
    /// @dev Thrown during snipe protection when purchasing tokens and the paired token balance decreases.
    error InsufficientPoolInput();
    /// @dev Thrown during deployment if the heavy snipe exponent exceeds type(uint8).max or is less than the base snipe exponent.
    error InvalidHeavySnipeParameters();
    /// @dev Thrown when calculating maximum amount out and the reserve balances are zero.
    error InvalidReserves();
    /// @dev Thrown during deployment if the token address is not greater than the paired token address.
    error InvalidTokenAddress();
    /// @dev Thrown during deployment if the deployer requests a deployer reserve greater than the maximum setting.
    error ReserveExceedsMax();
    /// @dev Thrown during deployment if the snipe protection time will overflow a uint40 value.
    error SnipeProtectionTimeOverflow();
    /// @dev Thrown when attempting to set the treasury wallet address to the zero address or the Uniswap V2 pair.
    error InvalidTreasuryAddress();
    /// @dev Thrown during deployment if the supply exceeds the maximum allowed by Uniswap V2.
    error SupplyExceedsMax();
    /// @dev Thrown when setting an approval or attempting to transfer tokens to the zero address.
    error ZeroAddress();

    /// @dev Emitted when an approval is updated.
    event Approval(address indexed owner, address indexed spender, uint256 value);
    /// @dev Emitted when deployer fees are updated.
    event DeployerFeesUpdated(uint16 buyFee, uint16 sellFee);
    /// @dev Emitted when a token balance is transfered.
    event Transfer(address indexed from, address indexed to, uint256 value);
    /// @dev Emitted when the treasury wallet is updated.
    event TreasuryWalletUpdated(address treasuryWallet);

    /**
     * @notice Constructs the g8keep token.
     *
     * @param _deployer                Address of the deployer of the token.
     * @param _name                    Name of the token being deployed.
     * @param _symbol                  Symbol of the token being deployed.
     * @param _totalSupply             Total supply to be minted during deployment.
     * @param _treasuryWallet          Address that will receive deployer buy and sell fees.
     * @param _penaltyTaxReceiver      Address that will receive penalty taxes.
     * @param _buyFee                  Buy fee in BPS.
     * @param _sellFee                 Sell fee in BPS.
     * @param _g8keepFee               Fee assessed by g8keep for trades in BPS.
     * @param _uniswapV2Router         Address of the Uniswap V2 Router.
     * @param _pairedToken             Address of the token to pair with in the LP.
     * @param _deployReserve           Amount of total supply in BPS to allocate to deployer.
     * @param _deployVestTime          Time in seconds to vest the deployer's allocation.
     * @param _snipeProtectionSeconds  Amount of time in seconds that snipe protection will be enabled.
     * @param _heavySnipeSeconds       Amount of time in seconds that heavy snipe penalty is enabled.
     * @param _heavySnipeExponent      Starting exponent for heavy snipe penalty.
     */
    constructor(
        address _deployer,
        string memory _name,
        string memory _symbol,
        uint256 _totalSupply,
        address _treasuryWallet,
        address _penaltyTaxReceiver,
        uint16 _buyFee,
        uint16 _sellFee,
        uint16 _g8keepFee,
        address _uniswapV2Router,
        address _pairedToken,
        uint256 _deployReserve,
        uint256 _deployVestTime,
        uint256 _snipeProtectionSeconds,
        uint256 _heavySnipeSeconds,
        uint256 _heavySnipeExponent
    ) {
        if (_deployReserve > MAX_DEPLOYER_RESERVE) revert ReserveExceedsMax();
        if (_snipeProtectionSeconds + block.timestamp > type(uint40).max || _heavySnipeSeconds > _snipeProtectionSeconds) revert SnipeProtectionTimeOverflow();
        if (_totalSupply > type(uint112).max) revert SupplyExceedsMax();
        if (_heavySnipeExponent > type(uint8).max || _heavySnipeExponent < SNIPE_PENALTY_BASE_EXPONENT) revert InvalidHeavySnipeParameters();

        _initializeOwner(_deployer);

        G8KEEP = msg.sender;
        PENALTY_TAX_RECEIVER = _penaltyTaxReceiver;
        GENESIS_TIME = uint40(block.timestamp);
        SNIPE_PROTECTION_SECONDS = uint40(_snipeProtectionSeconds);
        SNIPE_PROTECTION_END = uint40(_snipeProtectionSeconds + block.timestamp);
        G8KEEP_FEE = _g8keepFee;
        PAIRED_TOKEN = _pairedToken;
        SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS = uint40(_heavySnipeSeconds);
        SNIPE_PROTECTION_HEAVY_EXPONENT_START = uint8(_heavySnipeExponent - SNIPE_PENALTY_BASE_EXPONENT);
        buyFee = _buyFee;
        sellFee = _sellFee;

        //Revert if deployment address is less than the paired token address
        //Token must be "token1" in the v2 pool so that snipe protection can
        //evaluate the paired token balance of the pool while transferring
        //this token to the buyer.
        if (uint160(address(this)) < uint160(_pairedToken)) {
            revert InvalidTokenAddress();
        }

        name = _name;
        symbol = _symbol;
        totalSupply = _totalSupply;
        treasuryWallet = _treasuryWallet;

        IUniswapV2Router02 uniswapV2Router = IUniswapV2Router02(_uniswapV2Router);
        address uniswapV2Pair;
        try 
            IUniswapV2Factory(uniswapV2Router.factory()).createPair(_pairedToken, address(this))
            returns (address _uniswapV2Pair) {
            uniswapV2Pair = _uniswapV2Pair;
        } catch {
            uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).getPair(_pairedToken, address(this));
        }
        UNISWAP_V2_PAIR = uniswapV2Pair;

        if (_treasuryWallet == uniswapV2Pair) {
            revert InvalidTreasuryAddress();
        }

        uint256 tokensToDeployer = _totalSupply * _deployReserve / BPS;
        uint256 tokensToLP = _totalSupply - tokensToDeployer;
        lpBalanceBaseline = tokensToLP;

        // Transfer tokens for LP to factory, set allowance to router
        _balances[msg.sender] = tokensToLP;
        emit Transfer(address(0), msg.sender, tokensToLP);
        _allowances[msg.sender][_uniswapV2Router] = type(uint256).max;
        emit Approval(msg.sender, _uniswapV2Router, type(uint256).max);

        if (tokensToDeployer > 0) {
            if (_deployVestTime == 0) {
                // Transfer deployer tokens to deployer
                _balances[_deployer] = tokensToDeployer;
                emit Transfer(address(0), _deployer, tokensToDeployer);
            } else {
                address g8keepVester = IG8keepFactory(msg.sender).g8keepTokenVesting();
                _balances[g8keepVester] = tokensToDeployer;
                emit Transfer(address(0), g8keepVester, tokensToDeployer);

                IG8keepDeployerVesting(g8keepVester).deploymentVest(_deployer, tokensToDeployer, _deployVestTime);
            }
        }
    }

    /////////// EXTERNAL FUNCTIONS ///////////

    /**
     * @notice  Calculates the amount of tokens that may be purchased without a snipe
     * @notice  protection penalty.
     *
     * @return _amount  The amount of tokens that may be purchased without a snipe penalty.
     */
    function maxSnipeProtectionBuyWithoutPenalty() external view returns (uint256 _amount) {
        _amount = _balances[UNISWAP_V2_PAIR];
        if (block.timestamp < SNIPE_PROTECTION_END) {
            // Adjust balance for amounts added to the LP by third parties
            (,,uint256 cachedThirdPartyLPAmount) = _checkLPForIncreaseView();

            if (cachedThirdPartyLPAmount > 0) {
                if (cachedThirdPartyLPAmount > _amount) {
                    _amount = 0;
                } else {
                    unchecked {
                        _amount = _amount - cachedThirdPartyLPAmount;
                    }
                }
            }

            uint256 elapsedSeconds = block.timestamp - GENESIS_TIME;
            uint256 cachedLPBalanceBaseline = lpBalanceBaseline;
            uint256 expectedBalance1 =
                cachedLPBalanceBaseline - cachedLPBalanceBaseline * elapsedSeconds / SNIPE_PROTECTION_SECONDS;

            if (_amount < expectedBalance1) {
                _amount = 0;
            } else {
                _amount = _amount - expectedBalance1;
            }
        }
    }

    /**
     * @notice  Calculates the expected output amounts to buyer, penalty and fees for 
     * @notice  the `amount0In`, accounts for snipe protection penalties and current fee settings.
     *
     * @param amount0In  The amount of paired token that will be input to the pool.
     *
     * @return amount1Out         The amount the buyer will receive.
     * @return penaltyAmount1Out  The amount that will be sent to the penalty receiver.
     * @return feesAmount1Out     The total fees to g8keep and deployer treasury.
     */
    function expectedAmount1Out(uint256 amount0In) external view returns (uint256 amount1Out, uint256 penaltyAmount1Out, uint256 feesAmount1Out) {
        (uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) = _checkLPForIncreaseView();
        amount1Out = _getAmountOut(amount0In, reserve0, reserve1);

        if (block.timestamp < SNIPE_PROTECTION_END) {
            (amount1Out, penaltyAmount1Out) = _adjustAmountOut(amount1Out, cachedThirdPartyLPAmount, lpBalanceBaseline);
        }
        
        unchecked {
            uint256 deployerFee = buyFee;
            if (deployerFee > 0) {
                feesAmount1Out += (amount1Out * deployerFee) / BPS;
            }

            if (G8KEEP_FEE > 0) {
                feesAmount1Out += (amount1Out * G8KEEP_FEE) / BPS;
            }

            amount1Out = amount1Out - feesAmount1Out;
        }
    }

    /**
     * @notice  Returns the token balance of `account`.
     *
     * @param account  Address to retrieve the balance of.
     * 
     * @return _balance  The token balance of `account`.
     */
    function balanceOf(address account) public view returns (uint256 _balance) {
        _balance = _balances[account];
    }

    /**
     * @notice  Returns the amount `owner` has authorized `spender` to transfer.
     * 
     * @dev     An allowance of `type(uint256).max` is considered an unlimited allowance.
     * 
     * @param owner    The owner of the tokens `spender` is allowed to transfer.
     * @param spender  The account authorized by `owner` to spend tokens on behalf of.
     *
     * @return _allowance  The amount of tokens of `owner` that `spender` is allowed to transfer.
     */
    function allowance(address owner, address spender) public view returns (uint256 _allowance) {
        _allowance = _allowances[owner][spender];
    }

    /**
     * @notice  Sets the allowed amount that `spender` may transfer on behalf of the caller.
     *
     * @dev     An `amount` of `type(uint256).max` is considered an unlimited allowance.
     * 
     * @param spender  Address of the account that the caller is authorizing.
     * @param amount   Amount of tokens the caller is authorizing `spender` to transfer.
     */
    function approve(address spender, uint256 amount) external returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    /**
     * @notice  Transfers `amount` of tokens from the caller to `recipient`.
     *
     * @param recipient  Address that will receive the tokens from the caller.
     * @param amount     Amount of tokens to transfer to `recipient`.
     */
    function transfer(address recipient, uint256 amount) external returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    /**
     * @notice  Transfers `amount` of tokens from `sender` to `recipient` if the
     * @notice  caller has a sufficient allowance set by `sender`.
     *
     * @param sender     Account to send the tokens from.
     * @param recipient  Address that will receive the tokens from `sender`.
     * @param amount     Amount of tokens to transfer to `recipient` from `sender`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
        uint256 currentAllowance = _allowances[sender][msg.sender];
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < amount) revert InsufficientAllowance();
            unchecked {
                _approve(sender, msg.sender, currentAllowance - amount);
            }
        }

        _transfer(sender, recipient, amount);

        return true;
    }

    /////////// OWNER FUNCTIONS ///////////

    /**
     * @notice  Admin function for the token owner to set a new treasury wallet to receive fees.
     *
     * @dev     The address may not be set to the zero address or the Uniswap V2 Pair.
     *
     * @param newAddress  Address to set as the treasury wallet.
     */
    function updateTreasuryWallet(address newAddress) external onlyOwner {
        if (newAddress == address(0) || newAddress == UNISWAP_V2_PAIR) {
            revert InvalidTreasuryAddress();
        }
        treasuryWallet = newAddress;

        emit TreasuryWalletUpdated(newAddress);
    }

    /**
     * @notice  Admin function for the token owner to set new buy and sell fees.
     *
     * @dev     The buy and sell fees may not be set higher than the current fees.
     *
     * @param _buyFee   Fee in BPS to assess on token buys.
     * @param _sellFee  Fee in BPS to assess on token sells.
     */
    function updateFees(uint16 _buyFee, uint16 _sellFee) external onlyOwner {
        uint16 _oldBuyFee = buyFee;
        uint16 _oldSellfee = sellFee;
        if (_oldBuyFee < _buyFee || _oldSellfee < _sellFee) {
            revert CannotIncreaseFees();
        }
        
        buyFee = _buyFee;
        sellFee = _sellFee;

        emit DeployerFeesUpdated(_buyFee, _sellFee);
    }

    /**
     * @notice  Admin function for the token owner to recover tokens mistakenly sent 
     * @notice  to the token contract.
     * 
     * @dev     Will withdraw the entire balance of the token.
     * 
     * @param tokenAddress  Address of the token to withdraw.
     * @param to            Address to withdraw the token to.
     */
    function withdrawToken(address tokenAddress, address to) external onlyOwner {
        uint256 _contractBalance = IERC20(tokenAddress).balanceOf(address(this));
        SafeTransferLib.safeTransfer(tokenAddress, to, _contractBalance);
    }

    /**
     * @notice  Admin function for the token owner to recover tokens mistakenly sent 
     * @notice  to the token contract.
     * 
     * @dev     Will withdraw the specified `amount` of token.
     * 
     * @param tokenAddress  Address of the token to withdraw.
     * @param to            Address to withdraw the token to.
     * @param amount        Amount of the token to withdraw.
     */
    function withdrawToken(address tokenAddress, address to, uint256 amount) external onlyOwner {
        SafeTransferLib.safeTransfer(tokenAddress, to, amount);
    }

    /////////// INTERNAL FUNCTIONS ///////////

    /**
     * @dev  Internal function to handle approvals. 
     * @dev  Updates the `_allowances` mapping and emits an `Approval` event.
     * 
     * @param owner    Account that owns the tokens being authorized. This will always be the caller.
     * @param spender  Address of the account that the caller is authorizing.
     * @param amount   Amount of tokens the caller is authorizing `spender` to transfer.
     */
    function _approve(address owner, address spender, uint256 amount) private {
        if (owner == address(0)) revert ZeroAddress();
        if (spender == address(0)) revert ZeroAddress();

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev  Internal function to handle transfers. 
     * @dev  Adjusts `amount` based on snipe protection calculations while enabled.
     * @dev  Applies fees when the transfer is going to or coming from the Uniswap V2 Pair.
     * @dev  Updates the `_balances` mapping for `from` and `to` and emits a `Transfer` event.
     * 
     * @param from    Address of the account to transfer tokens from.
     * @param to      Address of the account to transfer tokens to.
     * @param amount  Amount of tokens to transfer from `from` to `to`.
     */
    function _transfer(address from, address to, uint256 amount) private {
        if (from == address(0)) revert ZeroAddress();
        if (to == address(0)) revert ZeroAddress();

        amount = _applySnipeProtection(from, to, amount);

        uint256 senderBalance = _balances[from];
        if (senderBalance < amount) revert InsufficientBalance();
        unchecked {
            _balances[from] = senderBalance - amount;
        }

        uint256 toAmount = amount;
        if (from != G8KEEP) {
            if (to == UNISWAP_V2_PAIR) {
                toAmount = _applyFees(from, amount, sellFee);
            } else if (from == UNISWAP_V2_PAIR) {
                toAmount = _applyFees(from, amount, buyFee);
            }
        }

        unchecked {
            _balances[to] += toAmount;
            emit Transfer(from, to, toAmount);
        }
    }

    /**
     * @dev  Internal function to apply buy and sell fees.
     * @dev  Updates the `_balances` mapping for `_treasuryWallet` and `G8KEEP`
     * @dev  and emits `Transfer` events if their calculated fees are greater than zero.
     * 
     * @param from         Address of the account to transfer tokens from.
     * @param amount       Amount of tokens to transfer from `from`.
     * @param deployerFee  Fee in BPS being assessed by the deployer.
     * 
     * @return toAmount  Amount of tokens to transfer to the recipient after fees are applied.
     */
    function _applyFees(
        address from,
        uint256 amount,
        uint256 deployerFee
    ) internal returns (uint256 toAmount) {
        unchecked {
            uint256 deployerFees;
            if (deployerFee > 0) {
                deployerFees = (amount * deployerFee) / BPS;

                address _treasuryWallet = treasuryWallet;
                _balances[_treasuryWallet] += deployerFees;
                emit Transfer(from, _treasuryWallet, deployerFees);
            }

            uint256 g8keepFees;
            if (G8KEEP_FEE > 0) {
                g8keepFees = (amount * G8KEEP_FEE) / BPS;
                _balances[G8KEEP] += g8keepFees;
                emit Transfer(from, G8KEEP, g8keepFees);
            }

            toAmount = amount - deployerFees - g8keepFees;
        }
    }

    /**
     * @dev  Internal function to check if snipe protection is enabled and adjust the output
     * @dev  amount if it is and the `amount` exceeds the maximum buy without a snipe penalty.
     *
     * @param from        Address of the account to transfer tokens from.
     * @param to          Address of the account to transfer tokens to.
     * @param amount1Out  Amount of tokens to transfer from `from` to `to`.
     *
     * @param adjustedAmount1Out  Amount of tokens to transfer after applying snipe protection.
     */
    function _applySnipeProtection(
        address from,
        address to,
        uint256 amount1Out
    ) internal returns (uint256 adjustedAmount1Out) {
        adjustedAmount1Out = amount1Out;
        if (block.timestamp < SNIPE_PROTECTION_END) {
            (uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) = _checkLPForIncrease();
            if (from == UNISWAP_V2_PAIR) {
                if (to == UNISWAP_V2_PAIR) {
                    revert CannotTransferFromAndToUniswapV2Pair();
                }

                // Early return to avoid revert on skims
                if (amount1Out == 0) return 0;

                // Check paired token balance for increase in pool, must increase during buy transaction
                uint256 balance0 = IERC20(PAIRED_TOKEN).balanceOf(UNISWAP_V2_PAIR);
                if (balance0 < reserve0) {
                    revert InsufficientPoolInput();
                }
                uint256 amount0In;
                unchecked {
                    amount0In = balance0 - reserve0;
                }
                
                // Calculate max output for input, transfer request cannot be more than calculated output
                uint256 maxAmount1Out = _getAmountOut(amount0In, reserve0, reserve1);
                if (amount1Out > maxAmount1Out) {
                    revert ExcessiveAmountOut();
                }

                // Adjust outputs for snipe protection, transfer penalty tokens to penalty receiver
                uint256 penaltyAmount1Out;
                uint256 cachedLPBalanceBaseline = lpBalanceBaseline;
                (adjustedAmount1Out, penaltyAmount1Out) = _adjustAmountOut(amount1Out, cachedThirdPartyLPAmount, cachedLPBalanceBaseline);
                if (penaltyAmount1Out > 0) {
                    lpBalanceBaseline = cachedLPBalanceBaseline - penaltyAmount1Out;
                    _balances[from] -= penaltyAmount1Out; // from is UNISWAP_V2_PAIR, optimizing bytecode by using stack
                    _balances[PENALTY_TAX_RECEIVER] += penaltyAmount1Out;
                    emit Transfer(from, PENALTY_TAX_RECEIVER, penaltyAmount1Out);
                }
            }
        }
    }

    /**
     * @dev  Internal function used during snipe protection to determine if tokens
     * @dev  have been added to the LP by a third party so that snipe protection can
     * @dev  be applied appropriately.
     * @dev  Returns the LP reserves for use in `_adjustAmountOut` for efficiency.
     * @dev  Returns the third party LP amount for use in `_adjustAmountOut` for efficiency.
     *
     * @return reserve0                  The reserve of `token0` in the LP when the token transfer is called.
     * @return reserve1                  The reserve of `token1` in the LP when the token transfer is called.
     * @return cachedThirdPartyLPAmount  Amount of tokens added to the LP by a third party.
     */
    function _checkLPForIncrease() internal returns (uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) {
        (reserve0, reserve1) = _getTokenReserves();
        cachedThirdPartyLPAmount = thirdPartyLPAmount;
        uint256 _cachedLPReserve1 = cachedLPReserve1;
        // Cache new reserve1 for future transactions
        cachedLPReserve1 = uint112(reserve1);

        uint256 _lpTotalSupply = IERC20(UNISWAP_V2_PAIR).totalSupply();
        uint256 _lastLPTotalSupply = lastLPTotalSupply;
        if (_lpTotalSupply != _lastLPTotalSupply) {
            // Check if LP has been burned
            if (_lpTotalSupply < _lastLPTotalSupply) {
                revert CannotBurnLiquidityDuringSnipeProtection();
            }

            lastLPTotalSupply = _lpTotalSupply;

            // Skip update on first change to LP total supply
            if (_lastLPTotalSupply != 0) {
                if (reserve1 > _cachedLPReserve1) {
                    // Underflow already checked
                    // Cannot overflow since total supply is limited to type(uint112).max
                    unchecked {
                        cachedThirdPartyLPAmount = cachedThirdPartyLPAmount + 
                            (_lpTotalSupply - _lastLPTotalSupply) * 
                            _cachedLPReserve1 / _lastLPTotalSupply;

                        thirdPartyLPAmount = uint112(cachedThirdPartyLPAmount);
                    }
                }
            }
        }
    }

    /**
     * @dev  Internal function used in view functions to determine if tokens
     * @dev  have been added to the LP by a third party so that snipe protection
     * @dev  calculations can be applied appropriately.
     * @dev  Returns the LP reserves for use in `_adjustAmountOut` for efficiency.
     * @dev  Returns the third party LP amount for use in `_adjustAmountOut` for efficiency.
     *
     * @return reserve0                  The reserve of `token0` in the LP when the view function is called.
     * @return reserve1                  The reserve of `token1` in the LP when the view function is called.
     * @return cachedThirdPartyLPAmount  Amount of tokens added to the LP by a third party.
     */
    function _checkLPForIncreaseView() internal view returns (uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) {
        cachedThirdPartyLPAmount = thirdPartyLPAmount;
        (reserve0,reserve1) = _getTokenReserves();

        uint256 _lpTotalSupply = IERC20(UNISWAP_V2_PAIR).totalSupply();
        if (!(_lpTotalSupply == lastLPTotalSupply || lastLPTotalSupply == 0)) {
            uint256 _cachedLPReserve1 = cachedLPReserve1;
            if (reserve1 > _cachedLPReserve1) {
                // Underflow already checked
                // Cannot overflow since total supply is limited to type(uint112).max
                unchecked {
                    cachedThirdPartyLPAmount = cachedThirdPartyLPAmount + reserve1 - _cachedLPReserve1;
                }
            }
        }
    }

    /**
     * @dev  Internal function to adjust the amount of tokens being purchased from the
     * @dev  Uniswap V2 Pair during snipe protection when the purchase amount exceeds the
     * @dev  maximum amount that can be purchased without a penalty.
     *
     * @dev  Tokens are released for purchase without snipe protection penalty linearly during
     * @dev  the snipe protection window and penalties increase exponentially when exceeding the
     * @dev  expected balance of the Uniswap V2 Pair.
     * @dev  For example: A token with 100,000 tokens in the initial LP and snipe protection time 
     * @dev    of 60 minutes will have 25,000 tokens available to purchase without penalty after
     * @dev    the token has been deployed for 15 minutes for an expected LP balance of 75,000 tokens.
     * @dev    A buyer that attempts to purchase 62,500 tokens would result in a 37,500 token balance
     * @dev    in the LP which is 50% of expected balance.
     * @dev    Their adjustedAmount1Out would equal 62,500 * 0.5^2 = 15,625 tokens (75% penalty).
     * @dev    Instead of attempting to buy 62,500 tokens, the user should buy the 25,000 tokens
     * @dev    that are available without snipe protection to avoid penalty.
     * @dev  Within the regular snipe protection window there is also a heavy penalty window. During
     * @dev  the heavy penalty window the exponent for applying the penalty starts at a higher value
     * @dev  and decreases linearly until it reaches the base exponent of 2.
     * @dev  For example: Using the example from above with a heavy snipe window of 10 minutes and
     * @dev    a starting penalty exponent of 12. After 6 minutes, there will be 10,000 tokens available
     * @dev    to purchase, expected balance 90,000 tokens, and the snipe penalty exponent will be 6. A 
     * @dev    buyer that attempts to purchase 55,000 tokens would result in a 45,000 token balance
     * @dev    in the LP which is 50% of the expected balance.
     * @dev    Their adjustedAmount1Out would equal 55,000 * 0.5^6 = 859.375 tokens (98.5% penalty).
     * @dev    Instead of attempting to purchase 55,0000 tokens, the user should buy the 10,000 tokens
     * @dev    that are available without snipe protection to avoid penalty.
     *
     * @param amount1Out                Amount of the token being purchased through the Uniswap V2 Pair.
     * @param cachedThirdPartyLPAmount  Amount of tokens added to the LP by a third party.
     * @param cachedLPBalanceBaseline   The baseline balance of the LP for snipe protection.
     *
     * @return adjustedAmount1Out  The amount out after adjusting for snipe protection.
     * @return penaltyAmount1Out   The amount out to the penalty receiver.
     */
    function _adjustAmountOut(
        uint256 amount1Out,
        uint256 cachedThirdPartyLPAmount,
        uint256 cachedLPBalanceBaseline
    ) internal view returns (uint256 adjustedAmount1Out, uint256 penaltyAmount1Out) {
        // Get balance, adjust by amount out
        uint256 balance1 = _balances[UNISWAP_V2_PAIR];
        if (amount1Out > balance1) {
            revert InsufficientBalance();
        }
        
        adjustedAmount1Out = amount1Out;
        uint256 adjustedBalance1;
        unchecked {
            adjustedBalance1 = balance1 - adjustedAmount1Out;
        }

        // Adjust balance for amounts added to the LP by third parties
        if (cachedThirdPartyLPAmount > 0) {
            if (cachedThirdPartyLPAmount > adjustedBalance1) {
                adjustedBalance1 = 0;
            } else {
                unchecked {
                    adjustedBalance1 = adjustedBalance1 - cachedThirdPartyLPAmount;
                }
            }
        }

        // Calculate expected balance based on elapsed time
        uint256 elapsedSeconds = block.timestamp - GENESIS_TIME;
        uint256 expectedBalance1 =
            cachedLPBalanceBaseline - cachedLPBalanceBaseline * elapsedSeconds / SNIPE_PROTECTION_SECONDS;

        // Apply snipe penalties if adjusted balance is less than expected
        if (expectedBalance1 > adjustedBalance1) {
            uint256 exponentPenalty = SNIPE_PENALTY_BASE_EXPONENT;
            if (elapsedSeconds < SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS) {
                unchecked {
                    exponentPenalty += 
                        SNIPE_PROTECTION_HEAVY_EXPONENT_START * 
                        (SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS - elapsedSeconds) / 
                        SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS;
                }
            }
            for (uint256 i; i < exponentPenalty; ++i) {
                adjustedAmount1Out = 
                    adjustedAmount1Out * adjustedBalance1 / expectedBalance1;
            }

            unchecked {
                penaltyAmount1Out = amount1Out - adjustedAmount1Out;
            }
        }
    }

    /**
     * @dev Internal function to get the token reserve balances from the Uniswap V2 Pair.
     *
     * @return reserve0  The reserve of `token0` in the LP.
     * @return reserve1  The reserve of `token1` in the LP.
     */
    function _getTokenReserves() internal view returns (uint256 reserve0, uint256 reserve1) {
        (reserve0,reserve1,) = IUniswapV2Pair(UNISWAP_V2_PAIR).getReserves();
    }

    /**
     * @dev  Internal function to calculate the amount of tokens expected to be received
     * @dev  from a purchase with `amountIn`. This is the same formula used by the 
     * @dev  Uniswap V2 Router when calculating output amount.
     * 
     * @param amountIn    The amount of tokens input to the pair.
     * @param reserveIn   The LP reserve of the input token.
     * @param reserveOut  The LP reserve of the token being purchased.
     *
     * @return amountOut  The calculated amount of output tokens.
     */
    function _getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256 amountOut) {
        if (reserveIn == 0 || reserveOut == 0) revert InvalidReserves();
        uint256 amountInWithFee = amountIn * 997;
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = reserveIn * 1000 + amountInWithFee;
        amountOut = numerator / denominator;
    }
}
设置
{
  "compilationTarget": {
    "src/g8keepToken.sol": "g8keepToken"
  },
  "evmVersion": "shanghai",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 999999
  },
  "remappings": [
    ":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    ":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
    ":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    ":forge-std/=lib/forge-std/src/",
    ":openzeppelin-contracts/=lib/openzeppelin-contracts/",
    ":solady/=lib/solady/src/",
    ":solmate/=lib/solmate/src/"
  ],
  "viaIR": true
}
ABI
[{"inputs":[{"internalType":"address","name":"_deployer","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint256","name":"_totalSupply","type":"uint256"},{"internalType":"address","name":"_treasuryWallet","type":"address"},{"internalType":"address","name":"_penaltyTaxReceiver","type":"address"},{"internalType":"uint16","name":"_buyFee","type":"uint16"},{"internalType":"uint16","name":"_sellFee","type":"uint16"},{"internalType":"uint16","name":"_g8keepFee","type":"uint16"},{"internalType":"address","name":"_uniswapV2Router","type":"address"},{"internalType":"address","name":"_pairedToken","type":"address"},{"internalType":"uint256","name":"_deployReserve","type":"uint256"},{"internalType":"uint256","name":"_deployVestTime","type":"uint256"},{"internalType":"uint256","name":"_snipeProtectionSeconds","type":"uint256"},{"internalType":"uint256","name":"_heavySnipeSeconds","type":"uint256"},{"internalType":"uint256","name":"_heavySnipeExponent","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CannotBurnLiquidityDuringSnipeProtection","type":"error"},{"inputs":[],"name":"CannotIncreaseFees","type":"error"},{"inputs":[],"name":"CannotTransferFromAndToUniswapV2Pair","type":"error"},{"inputs":[],"name":"ExcessiveAmountOut","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InsufficientPoolInput","type":"error"},{"inputs":[],"name":"InvalidHeavySnipeParameters","type":"error"},{"inputs":[],"name":"InvalidReserves","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[],"name":"InvalidTreasuryAddress","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"ReserveExceedsMax","type":"error"},{"inputs":[],"name":"SnipeProtectionTimeOverflow","type":"error"},{"inputs":[],"name":"SupplyExceedsMax","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"buyFee","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"sellFee","type":"uint16"}],"name":"DeployerFeesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"treasuryWallet","type":"address"}],"name":"TreasuryWalletUpdated","type":"event"},{"inputs":[],"name":"G8KEEP_FEE","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAIRED_TOKEN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_END","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_HEAVY_EXPONENT_START","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_SECONDS","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNISWAP_V2_PAIR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"_allowance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"_balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"buyFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0In","type":"uint256"}],"name":"expectedAmount1Out","outputs":[{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"uint256","name":"penaltyAmount1Out","type":"uint256"},{"internalType":"uint256","name":"feesAmount1Out","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSnipeProtectionBuyWithoutPenalty","outputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"sellFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"treasuryWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"_buyFee","type":"uint16"},{"internalType":"uint16","name":"_sellFee","type":"uint16"}],"name":"updateFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAddress","type":"address"}],"name":"updateTreasuryWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"}]