TokenStrategyFactory

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/TokenStrategyFactory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Ownable} from "solady/src/auth/Ownable.sol";
import {TokenStrategy} from "./TokenStrategy.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IUniswapV4Router04} from "../lib/v4-router/interfaces/IUniswapV4Router04.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import "./Interfaces.sol";
import {ReentrancyGuard} from "solady/src/utils/ReentrancyGuard.sol";
import {LibClone} from "solady/src/utils/LibClone.sol";

contract TokenStrategyFactory is Ownable, ReentrancyGuard {
    
    /************************************************************************************************************************************
    *    ██████╗ ███╗   ██╗██╗  ██╗███████╗████████╗██████╗     ███████╗████████╗██████╗  █████╗ ████████╗███████╗ ██████╗ ██╗   ██╗    *
    *    ██╔══██╗████╗  ██║██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗    ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝ ╚██╗ ██╔╝    *
    *    ██████╔╝██╔██╗ ██║█████╔╝ ███████╗   ██║   ██████╔╝    ███████╗   ██║   ██████╔╝███████║   ██║   █████╗  ██║  ███╗ ╚████╔╝     *    
    *    ██╔═══╝ ██║╚██╗██║██╔═██╗ ╚════██║   ██║   ██╔══██╗    ╚════██║   ██║   ██╔══██╗██╔══██║   ██║   ██╔══╝  ██║   ██║  ╚██╔╝      *
    *    ██║     ██║ ╚████║██║  ██╗███████║   ██║   ██║  ██║    ███████║   ██║   ██║  ██║██║  ██║   ██║   ███████╗╚██████╔╝   ██║       *
    *    ╚═╝     ╚═╝  ╚═══╝╚═╝  ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝    ╚══════╝   ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝    ╚═╝       *
    *************************************************************************************************************************************/

    uint256 private constant ethToPair = 2 wei;
    uint256 private constant initialBuy = 0.01 ether;
    IPositionManager private immutable posm;
    IAllowanceTransfer private immutable permit2;
    IUniswapV4Router04 private immutable router;
    address private immutable poolManager;

    address public constant DEADADDRESS =
        0x000000000000000000000000000000000000dEaD;

    mapping(address => address) public nftToTokenStrategy;
    mapping(address => address) public tokenToNftStrategy;
    uint256 public feeToLaunch;
    address public hookAddress;
    address public feeAddress;
    bool public loadingLiquidity;
    bool public deployerBuying;
    bool public publicLaunches;
    bool public collectionOwnerLaunches;
    bool public migrated = false;

    bool public routerRestrict;
    mapping(address => bool) public listOfRouters;

    error HookNotSet();
    error CollectionAlreadyLaunched();
    error WrongEthAmount();
    error NotERC721();
    error GatedByCollectionOwner();
    error CannotLaunch();
    error NoETHToTwap();
    error TwapDelayNotMet();
    error InvalidNFTStrategy();
    error NotAContract();
    error MigrationAlreadyCompleted();
    error NotEnoughToken();

    event TokenStrategyLaunched(
        address indexed tokenWorkStrategy,
        address indexed tokenStrategy,
        string tokenName,
        string tokenSymbol
    );

    constructor(
        address _posm,
        address _permit2,
        address _poolManager,
        address _universalRouter,
        address payable _router,
        address _feeAddress
    ) {
        router = IUniswapV4Router04(_router);
        posm = IPositionManager(_posm);
        permit2 = IAllowanceTransfer(_permit2);
        poolManager = _poolManager;

        listOfRouters[address(this)] = true;
        listOfRouters[_posm] = true;
        listOfRouters[_permit2] = true;
        listOfRouters[_router] = true;
        listOfRouters[_universalRouter] = true;
        listOfRouters[DEADADDRESS] = true;

        routerRestrict = false;

        feeAddress = _feeAddress;
        _initializeOwner(msg.sender);
    }

    function setPublicLaunches(bool _publicLaunches) external onlyOwner {
        publicLaunches = _publicLaunches;
    }

    function setCollectionOwnerLaunches(
        bool _collectionOwnerLaunches
    ) external onlyOwner {
        collectionOwnerLaunches = _collectionOwnerLaunches;
    }

    function setRouter(address _router, bool status) external onlyOwner {
        listOfRouters[_router] = status;
    }

    function setRouterRestrict(bool status) external onlyOwner {
        routerRestrict = status;
    }

    function updateFeeToLaunch(uint256 _feeToLaunch) external onlyOwner {
        feeToLaunch = _feeToLaunch;
    }

    function updateHookAddress(address _hookAddress) external onlyOwner {
        hookAddress = _hookAddress;
        listOfRouters[hookAddress] = true;
    }

    function updateTokenName(
        address tokenStrategy,
        string memory tokenName
    ) external onlyOwner {
        ITokenStrategy(tokenStrategy).updateName(tokenName);
    }

    function updateTokenSymbol(
        address tokenStrategy,
        string memory tokenSymbol
    ) external onlyOwner {
        ITokenStrategy(tokenStrategy).updateSymbol(tokenSymbol);
    }

   function updatePriceMultiplier(
        address tokenStrategy,
        uint256 newMultiplier
    ) external onlyOwner {
        ITokenStrategy(tokenStrategy).setPriceMultiplier(newMultiplier);
    }

    function updateTargetFeeBuyToken(
        address tokenStrategy,
        uint256 newTargetFee
    ) external onlyOwner {
        ITokenStrategy(tokenStrategy).setTargetFeeBuyToken(newTargetFee);
    }

    function withdrawAllFees() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }

    function withdrawERC20Fees(address token) external onlyOwner {
        IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this)));
    }

    function _loadLiquidity(address _token) internal {
        loadingLiquidity = true;

        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)), // ETH
            Currency.wrap(_token), // TokenStrategy Token
            0, // lpFee
            60, // tickSpacing
            IHooks(hookAddress)
        );

        int24 tickLower = TickMath.minUsableTick(60);
        int24 tickUpper = int24(175020);

        // Hardcoded from LiquidityAmounts.getLiquidityForAmounts
        uint128 liquidity = 158372218983990412488087;

        uint256 amount0Max = 2; // 1 wei + 1 wei
        uint256 amount1Max = 1_000_000_000 * 10 ** 18 + 1 wei;

        (
            bytes memory actions,
            bytes[] memory mintParams
        ) = _mintLiquidityParams(
                key,
                tickLower,
                tickUpper,
                liquidity,
                amount0Max,
                amount1Max,
                address(this),
                new bytes(0)
            );

        bytes[] memory params = new bytes[](2);

        params[0] = abi.encodeWithSelector(
            posm.initializePool.selector,
            key,
            uint160(501082896750095888663770159906816), // startingPrice: 10e18 ETH = 1_000_000_000e18 TOKEN
            new bytes(0)
        );

        params[1] = abi.encodeWithSelector(
            posm.modifyLiquidities.selector,
            abi.encode(actions, mintParams),
            block.timestamp + 60
        );

        permit2.approve(
            _token,
            address(posm),
            type(uint160).max,
            type(uint48).max
        );

        posm.multicall{value: amount0Max}(params);

        loadingLiquidity = false;
    }

    function _mintLiquidityParams(
        PoolKey memory poolKey,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 liquidity,
        uint256 amount0Max,
        uint256 amount1Max,
        address recipient,
        bytes memory hookData
    ) internal pure returns (bytes memory, bytes[] memory) {
        bytes memory actions = abi.encodePacked(
            uint8(Actions.MINT_POSITION),
            uint8(Actions.SETTLE_PAIR)
        );

        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(
            poolKey,
            _tickLower,
            _tickUpper,
            liquidity,
            amount0Max,
            amount1Max,
            recipient,
            hookData
        );
        params[1] = abi.encode(poolKey.currency0, poolKey.currency1);
        return (actions, params);
    }

    function _buyTokens(
        uint256 amountIn,
        address nftStrategy,
        address caller
    ) internal {
        deployerBuying = true;

        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(nftStrategy),
            0,
            60,
            IHooks(hookAddress)
        );

        router.swapExactTokensForTokens{value: amountIn}(
            amountIn,
            0,
            true,
            key,
            "",
            caller,
            block.timestamp
        );

        deployerBuying = false;
    }

    function migrate(address[] memory _to, uint256[] memory _amount, TokenStrategy tokenStrategy) external payable onlyOwner {
        if (migrated) revert MigrationAlreadyCompleted();
        migrated = true;
        // buy with fee 0
        deployerBuying = true;
        
        uint256 totalAmount = 0;
        for (uint256 i = 0; i < _amount.length; i++) {
            totalAmount += _amount[i];
        }

        uint256 amountTokenBefore = tokenStrategy.balanceOf(address(this));
        uint256 ethBalanceBefore = address(this).balance;

        PoolKey memory key1 = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(address(tokenStrategy)),
            0,
            60,
            IHooks(tokenStrategy.hookAddress())
        );

        router.swapTokensForExactTokens{value: msg.value}(
            totalAmount,
            msg.value,
            true,
            key1,
            "",
            address(this),
            block.timestamp
        );

        uint256 amountTokenAfter = tokenStrategy.balanceOf(address(this));
        uint256 amountToken = amountTokenAfter - amountTokenBefore;

        if (amountToken < totalAmount) revert NotEnoughToken();
        
        uint256 ethAfterSwap = address(this).balance;
        uint256 ethUsed = ethBalanceBefore - ethAfterSwap;
        uint256 unusedEth = msg.value - ethUsed;

        for (uint256 i = 0; i < _to.length; i++) {
            tokenStrategy.transfer(_to[i], _amount[i]);
        }

        deployerBuying = false;

        // buy with default fee
        PoolKey memory key2 = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(address(tokenStrategy)),
            0,
            60,
            IHooks(tokenStrategy.hookAddress())
        );

        router.swapExactTokensForTokens{value: unusedEth}(
            unusedEth,
            0,
            true,
            key2,
            "",
            DEADADDRESS,
            block.timestamp
        );
    }

    function ownerLaunchNFTStrategy(
        address nftStrategy,
        string memory tokenName,
        string memory tokenSymbol,
        address collectionOwner
    ) external payable onlyOwner returns (TokenStrategy) {
        // Validate the parameters passed
        if (hookAddress == address(0)) revert HookNotSet();
        if (nftToTokenStrategy[nftStrategy] != address(0)) revert CollectionAlreadyLaunched();
        if (nftStrategy.code.length == 0) revert NotAContract();

        TokenStrategy tokenStrategy = new TokenStrategy(
            address(this),
            hookAddress,
            router,
            nftStrategy,
            tokenName,
            tokenSymbol,
            owner() 
        );

        nftToTokenStrategy[nftStrategy] = address(tokenStrategy);
        tokenToNftStrategy[address(tokenStrategy)] = nftStrategy;

        _loadLiquidity(address(tokenStrategy));

        ITokenStrategyHook(hookAddress).adminUpdateFeeAddress(
            address(tokenStrategy),
            collectionOwner
        );

        emit TokenStrategyLaunched(
            nftStrategy,
            address(tokenStrategy),
            tokenName,
            tokenSymbol
        );

        return tokenStrategy;
    }

    function launchNFTStrategy(
        address tokenWorkStrategy,
        string memory tokenName,
        string memory tokenSymbol
    ) external payable nonReentrant returns (TokenStrategy) {
        if (hookAddress == address(0)) revert HookNotSet();
        if (nftToTokenStrategy[tokenWorkStrategy] != address(0))
            revert CollectionAlreadyLaunched();
        if (msg.value != feeToLaunch) revert WrongEthAmount();
        if (!publicLaunches && !collectionOwnerLaunches) revert CannotLaunch();

        address collectionOwnerFromContract;

        if (!publicLaunches && msg.sender != collectionOwnerFromContract)
            revert GatedByCollectionOwner();

        TokenStrategy tokenStrategy = new TokenStrategy(
            address(this),
            hookAddress,
            router,
            tokenWorkStrategy,
            tokenName,
            tokenSymbol,
            owner()
        );

        nftToTokenStrategy[tokenWorkStrategy] = address(tokenStrategy);
        tokenToNftStrategy[address(tokenStrategy)] = tokenWorkStrategy;

        _loadLiquidity(address(tokenStrategy));

        if (collectionOwnerFromContract != address(0)) {
            ITokenStrategyHook(hookAddress).adminUpdateFeeAddress(
                address(tokenStrategy),
                collectionOwnerFromContract
            );
        } else {
            ITokenStrategyHook(hookAddress).adminUpdateFeeAddress(
                address(tokenStrategy),
                feeAddress
            );
        }

        _buyTokens(initialBuy, address(tokenStrategy), msg.sender);

        uint256 ethToSend = msg.value - ethToPair - initialBuy;
        SafeTransferLib.forceSafeTransferETH(feeAddress, ethToSend);

        emit TokenStrategyLaunched(
            tokenWorkStrategy,
            address(tokenStrategy),
            tokenName,
            tokenSymbol
        );

        return tokenStrategy;
    }

    function validTransfer(
        address from,
        address to,
        address tokenAddress
    ) external view returns (bool) {
        if (!routerRestrict) return true;

        bool userToUser = !listOfRouters[from] && !listOfRouters[to];
        if (userToUser && (from != tokenAddress && to != tokenAddress)) {
            if (from == address(poolManager)) return true;

            if (to == address(poolManager)) {
                return
                    ITokenStrategy(tokenAddress).midSwap() || loadingLiquidity;
            }
            return false;
        }
        return true;
    }

    receive() external payable {
    }

   
}
"
    },
    "node_modules/solady/src/auth/Ownable.sol": {
      "content": "// 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();
        _;
    }
}
"
    },
    "contracts/TokenStrategy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Ownable} from "solady/src/auth/Ownable.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/src/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IUniswapV4Router04} from "../lib/v4-router/interfaces/IUniswapV4Router04.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import "./Interfaces.sol";

contract TokenStrategy is ERC20, ReentrancyGuard, Ownable {
    /*********************************************************************************************************************************
    * ██████╗ ███╗   ██╗██╗  ██╗███████╗████████╗██████╗     ███████╗████████╗██████╗  █████╗ ████████╗███████╗ ██████╗ ██╗   ██╗    *
    * ██╔══██╗████╗  ██║██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗    ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔════╝ ╚██╗ ██╔╝    *
    * ██████╔╝██╔██╗ ██║█████╔╝ ███████╗   ██║   ██████╔╝    ███████╗   ██║   ██████╔╝███████║   ██║   █████╗  ██║  ███╗ ╚████╔╝     * 
    * ██╔═══╝ ██║╚██╗██║██╔═██╗ ╚════██║   ██║   ██╔══██╗    ╚════██║   ██║   ██╔══██╗██╔══██║   ██║   ██╔══╝  ██║   ██║  ╚██╔╝      *
    * ██║     ██║ ╚████║██║  ██╗███████║   ██║   ██║  ██║    ███████║   ██║   ██║  ██║██║  ██║   ██║   ███████╗╚██████╔╝   ██║       *
    * ╚═╝     ╚═╝  ╚═══╝╚═╝  ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝    ╚══════╝   ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝    ╚═╝       *
    **********************************************************************************************************************************/
   
    /// @notice The main NFTStrategy standard address
    INFTStrategy public nftStrategy;

    string tokenName;
    string tokenSymbol;
    
    /// @notice The maximum supply of the ERC20 token (1 billion)
    uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
    /// @notice The dead address for burning TokenStrategy
    address public constant DEADADDRESS = 0x000000000000000000000000000000000000dEaD;

    address public immutable PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /// @notice The target fee for buying the token
    uint256 public targetFeeBuyToken = 5 ether; 
    /// @notice The price multiplier for the token (1.2x)
    uint256 public priceMultiplier = 1200;
    
    uint256 public percentReward = 1; // 0.1%

    uint256 public lastBuyBlock;

    uint256 public lastSellBlock;

    struct TokenOrder {
        /// @notice The amount of ETH used to buy the token
        uint256 purchaseAmount;      
        /// @notice The target price for the token
        uint256 targetPrice;         
        /// @notice The purchase time for the token
        uint256 purchaseTime;        
        /// @notice The amount of token bought
        uint256 tokenAmount;         
        /// @notice The order is active or not
        bool isActive;               
    }

    /// @notice The address of the hook associated with this TokenStrategy
    address public immutable hookAddress;
    /// @notice The address of the factory associated with this TokenStrategy
    address public immutable factory;

    IUniswapV4Router04 private immutable router;

    uint256 public currentFees;
    uint256 public ethToTwap;
    uint256 public twapIncrement = 1 ether;
    uint256 public twapDelayInBlocks = 1;
    uint256 public lastTwapBlock;
    bool public midSwap;
   

    mapping(uint256 => TokenOrder) public tokenOrders;
    
    uint256 public nextOrderId;
    

    event TokenOrderCreated(
        uint256 indexed orderId,
        uint256 purchaseAmount,
        uint256 targetPrice,
        uint256 tokenAmount,
        uint256 purchaseTime
    );

    event TokenOrderSold(
        uint256 indexed orderId,
        uint256 ethReceived,
        uint256 tokenAmount,
        address caller
    );

    event AllowanceIncreased(uint256 amount);

    error OrderNotForSale();
    error TokenPriceTooLow();
    error InvalidMultiplier();
    error NoETHToTwap();
    error TwapDelayNotMet();
    error NotEnoughEth();
    error NotEnoughToken();
    error NotFactory();
    error OnlyHook();
    error NotValidRouter();
    error NotOwner();
    error NotEnoughFees();
    error TooSoon();
    
    constructor(
        address _factory,
        address _hook,
        IUniswapV4Router04 _router,
        address _nftStrategy,
        string memory _tokenName,
        string memory _tokenSymbol,
        address _owner
    ) {
        factory = _factory;
        router = _router;
        hookAddress = _hook;
        tokenName = _tokenName;
        tokenSymbol = _tokenSymbol;

        nftStrategy = INFTStrategy(_nftStrategy);

        _initializeOwner(_owner);
        _mint(factory, MAX_SUPPLY);

        nftStrategy.approve(PERMIT2_ADDRESS, type(uint256).max);
        IPermit2(PERMIT2_ADDRESS).approve(
            _nftStrategy,
            address(router),
            type(uint160).max,
            type(uint48).max
        );

        lastBuyBlock = block.number;
        lastSellBlock = block.number;
    }

    function name() public view override returns (string memory) {
        return tokenName;
    }

    function symbol() public view override returns (string memory) {
        return tokenSymbol;
    }

    function updateName(string memory _tokenName) external {
        if (msg.sender != factory) revert NotFactory();
        tokenName = _tokenName;
    }

    function updateSymbol(string memory _tokenSymbol) external {
        if (msg.sender != factory) revert NotFactory();
        tokenSymbol = _tokenSymbol;
    }

    function setPriceMultiplier(uint256 _newMultiplier) external {
        if (msg.sender != factory) revert NotFactory();
        if (_newMultiplier < 1100 || _newMultiplier > 10000)
            revert InvalidMultiplier();
        priceMultiplier = _newMultiplier;
    }

    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    /*                 MECHANISM FUNCTIONS                 */
    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */

    //set target fee for buying the token
    function setTargetFeeBuyToken(uint256 _newTargetFee) external {
        if (msg.sender != factory) revert NotFactory();
        targetFeeBuyToken = _newTargetFee;
    }

    function addFees() external payable nonReentrant {
        if (msg.sender != hookAddress) revert OnlyHook();
        currentFees += msg.value;
    }

    function buyTokenAndCreateOrder() external nonReentrant returns (uint256) {
        if (block.number < lastBuyBlock + 1) revert TooSoon();
        uint256 reward = (targetFeeBuyToken * percentReward) / 1000;
        if (currentFees < targetFeeBuyToken + reward) {
            revert NotEnoughFees();
        }
        uint256 orderId = _buyTokenAndCreateOrder(targetFeeBuyToken);
        SafeTransferLib.forceSafeTransferETH(msg.sender, reward);
        lastBuyBlock = block.number;
        return orderId;
    }

    function _buyTokenAndCreateOrder(uint256 purchaseAmount) internal returns (uint256 orderId) {
        if (purchaseAmount > currentFees) {
            revert NotEnoughEth();
        }

        uint256 tokenBalanceBefore = nftStrategy.balanceOf(address(this));

        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(address(nftStrategy)),
            0,
            60,
            IHooks(nftStrategy.hookAddress())
        );

        router.swapExactTokensForTokens{value: purchaseAmount}(
            purchaseAmount,
            0,
            true,
            key,
            "",
            address(this),
            block.timestamp
        );

        uint256 tokenBalanceAfter = nftStrategy.balanceOf(address(this));
        uint256 tokenAmount = tokenBalanceAfter - tokenBalanceBefore;

        currentFees -= purchaseAmount;

        uint256 targetPrice = (purchaseAmount * priceMultiplier) / 1000;

        orderId = nextOrderId;
        tokenOrders[orderId] = TokenOrder({
            purchaseAmount: purchaseAmount,
            targetPrice: targetPrice,
            purchaseTime: block.timestamp,
            tokenAmount: tokenAmount,
            isActive: true
        });

        nextOrderId++;

        emit TokenOrderCreated(
            orderId,
            purchaseAmount,
            targetPrice,
            tokenAmount,
            block.timestamp
        );

        return orderId;
    }

    function setMidSwap(bool value) external {
        if (msg.sender != hookAddress) revert OnlyHook();
        midSwap = value;
    }

    function sellTargetOrder(uint256 orderId) external nonReentrant {
        if (block.number < lastSellBlock + 1) revert TooSoon();
        TokenOrder storage order = tokenOrders[orderId];

        if (!order.isActive) revert OrderNotForSale();

        if (nftStrategy.balanceOf(address(this)) < order.tokenAmount) revert NotEnoughToken();

        uint256 ethBalanceBefore = address(this).balance;

        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(address(nftStrategy)),
            0,
            60,
            IHooks(nftStrategy.hookAddress())
        );

        router.swapExactTokensForTokens(
            order.tokenAmount,
            order.targetPrice, 
            false, 
            key,
            "",
            address(this),
            block.timestamp
        );

        // Calculate ETH received
        uint256 ethReceived = address(this).balance - ethBalanceBefore;

        if (ethReceived < order.targetPrice) revert TokenPriceTooLow();

        order.isActive = false;
        uint256 reward = (ethReceived * percentReward) / 1000;
        SafeTransferLib.forceSafeTransferETH(msg.sender, reward);

        ethToTwap += (ethReceived - reward);
        lastSellBlock = block.number;

        emit TokenOrderSold(orderId, ethReceived, order.tokenAmount, msg.sender);
    }

    function depositEthToTwap() external payable {
        ethToTwap += msg.value;
    }

    function processTokenTwap() external {
        if (ethToTwap == 0) revert NoETHToTwap();

        // Check if enough blocks have passed since last TWAP
        if (block.number < lastTwapBlock + twapDelayInBlocks)
            revert TwapDelayNotMet();

        // Calculate amount to burn - either twapIncrement or remaining ethToTwap
        uint256 burnAmount = twapIncrement;
        if (ethToTwap < twapIncrement) {
            burnAmount = ethToTwap;
        }

        uint256 reward = (burnAmount * percentReward) / 1000;
        burnAmount -= reward;

        // Update state
        ethToTwap -= burnAmount + reward;
        lastTwapBlock = block.number;

        _buyAndBurnTokens(burnAmount);

        SafeTransferLib.forceSafeTransferETH(msg.sender, reward);
    }


    function _buyAndBurnTokens(uint256 amountIn) internal {
        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(address(this)),
            0,
            60,
            IHooks(hookAddress)
        );

        router.swapExactTokensForTokens{value: amountIn}(
            amountIn,
            0,
            true,
            key,
            "",
            DEADADDRESS,
            block.timestamp
        );
    }

    function updatePercentReward(uint256 _newPercentReward) external onlyOwner {
        percentReward = _newPercentReward;
    }

    function _afterTokenTransfer(address from, address to, uint256) internal view override {
        if (!ITokenStrategyFactory(factory).routerRestrict() || midSwap) return;

        if (!ITokenStrategyFactory(factory).validTransfer(from, to, address(this))) {
            revert NotValidRouter();
        }
    }

    function withdrawAllFeesEmergency() external onlyOwner {
        SafeTransferLib.forceSafeTransferETH(msg.sender, address(this).balance);
    }

    function withdrawERC20Emergency(address token) external onlyOwner {
        IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this)));
    }

    receive() external payable {}
}
"
    },
    "node_modules/solady/src/utils/SafeTransferLib.sol": {
      "content": "// 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.
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 ERC20 `totalSupply` query has failed.
    error TotalSupplyQueryFailed();

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

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

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

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

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         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 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;

    /// @dev The canonical address of the `SELFDESTRUCT` ETH mover.
    /// See: https://gist.github.com/Vectorized/1cb8ad4cf393b1378e08f23f79bd99fa
    /// [Etherscan](https://etherscan.io/address/0x00000000000073c48c8055bD43D1A53799176f0D)
    address internal constant ETH_MOVER = 0x00000000000073c48c8055bD43D1A53799176f0D;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       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)
        }
    }

    /// @dev Force transfers ETH to `to`, without triggering the fallback (if any).
    /// This method attempts to use a separate contract to send via `SELFDESTRUCT`,
    /// and upon failure, deploys a minimal vault to accrue the ETH.
    function safeMoveETH(address to, uint256 amount) internal returns (address vault) {
        /// @solidity memory-safe-assembly
        assembly {
            to := shr(96, shl(96, to)) // Clean upper 96 bits.
            for { let mover := ETH_MOVER } iszero(eq(to, address())) {} {
                let selfBalanceBefore := selfbalance()
                if or(lt(selfBalanceBefore, amount), eq(to, mover)) {
                    mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                    revert(0x1c, 0x04)
                }
                if extcodesize(mover) {
                    let balanceBefore := balance(to) // Check via delta, in case `SELFDESTRUCT` is bricked.
                    mstore(0x00, to)
                    pop(call(gas(), mover, amount, 0x00, 0x20, codesize(), 0x00))
                    // If `address(to).balance >= amount + balanceBefore`, skip vault workflow.
                    if iszero(lt(balance(to), add(amount, balanceBefore))) { break }
                    // Just in case `SELFDESTRUCT` is changed to not revert and do nothing.
                    if lt(selfBalanceBefore, selfbalance()) { invalid() }
                }
                let m := mload(0x40)
                // If the mover is missing or bricked, deploy a minimal vault
                // that withdraws all ETH to `to` when being called only by `to`.
                // forgefmt: disable-next-item
                mstore(add(m, 0x20), 0x33146025575b600160005260206000f35b3d3d3d3d47335af1601a5760003dfd)
                mstore(m, or(to, shl(160, 0x6035600b3d3960353df3fe73)))
                // Compute and store the bytecode hash.
                mstore8(0x00, 0xff) // Write the prefix.
                mstore(0x35, keccak256(m, 0x40))
                mstore(0x01, shl(96, address())) // Deployer.
                mstore(0x15, 0) // Salt.
                vault := keccak256(0x00, 0x55)
                pop(call(gas(), vault, amount, codesize(), 0x00, codesize(), 0x00))
                // The vault returns a single word on success. Failure reverts with empty data.
                if iszero(returndatasize()) {
                    if iszero(create2(0, m, 0x40, 0)) { revert(codesize(), codesize()) } // For gas estimation.
                }
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      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)`.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    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 := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
            }
            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.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    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.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    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.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    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)`.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    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.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    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 ba

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Non-Fungible, Swap, Liquidity, Upgradeable, Multi-Signature, Factory|addr:0x5e41d0b6f4cda4c2fffc2b0b3cb1203afc987948|verified:true|block:23578080|tx:0xee6c971a34b5900b5915c0722b25a79d7156dbf11c58b0117d3893ba181e7c3c|first_check:1760512331

Submitted on: 2025-10-15 09:12:13

Comments

Log in to comment.

No comments yet.