MachineFunLauncher

0xb9c0d81f2380c2c69b560aebb84c6954d584e038

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,672 bytes
ABI entries
52
16 read · 15 write
License
none

Contract information

Address
0xb9c0d81f2380c2c69b560aebb84c6954d584e038
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x0f43a37d42…e297283d0f

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (16)

LP_FEE()uint24
TICK_SPACING()int24
TOKEN_SUPPLY()uint256
allTokens(uint256)address
allTokensLength()uint256
buyTaxBps()uint16
creatorFeeBps()uint16
defaultStartTick()int24
feeHook()address
launchFee()uint256
maxBuyBps()uint16
owner()address
poolManager()address
protocolFeeRecipient()address
sellTaxBps()uint16
tokenInfo(address)address, int24, int24, uint128, tuple

Events (9)

CreatorFeeBpsSetDefaultStartTickSetFeesCollectedLaunchFeeSetLiquidityRemovedMaxBuyBpsSetOwnershipTransferredProtocolFeeRecipientSetTokenLaunched

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "protocolFeeRecipient_",
        "type": "address"
      },
      {
        "internalType": "contract MachineFeeHook",
        "name": "feeHook_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "EthTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FeeShareTooHigh",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidStartTick",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoLiquidity",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPoolManager",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnexpectedEthOwed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "WrongLaunchFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "CreatorFeeBpsSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "int24",
        "name": "startTick",
        "type": "int24"
      }
    ],
    "name": "DefaultStartTickSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      }
    ],
    "name": "FeesCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "LaunchFeeSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "LiquidityRemoved",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "MaxBuyBpsSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "ProtocolFeeRecipientSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "PoolId",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "startTick",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensInPool",
        "type": "uint256"
      }
    ],
    "name": "TokenLaunched",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "LP_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "allTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "allTokensLength",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "buyTaxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "collectFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creatorFeeBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultStartTick",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeHook",
    "outputs": [
      {
        "internalType": "contract MachineFeeHook",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "int24",
        "name": "startTickOverride",
        "type": "int24"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "PoolId",
        "name": "poolId",
        "type": "bytes32"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxBuyBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolFeeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "removeLiquidity",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sellTaxBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "setCreatorFeeBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "int24",
        "name": "startTick",
        "type": "int24"
      }
    ],
    "name": "setDefaultStartTick",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "buyBps",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "sellBps",
        "type": "uint16"
      }
    ],
    "name": "setInitialTaxes",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "setLaunchFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "setMaxBuyBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setProtocolFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "exempt",
        "type": "bool"
      }
    ],
    "name": "setTokenExempt",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setTokenLimits",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "tokenInfo",
    "outputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "poolKey",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "rawData",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawLaunchFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";

import {MachineToken} from "./MachineToken.sol";
import {MachineFeeHook} from "./MachineFeeHook.sol";

/// @title MachineFunLauncher (v2) — machine.fun Uniswap v4 launchpad
///
/// @notice One-transaction fair launches on Uniswap v4:
///
///   1. Deploys a fixed-supply ERC-20 (1B tokens, max-buy limited).
///   2. Creates a native-ETH/token v4 pool (1% LP fee, no hook).
///   3. Deposits the ENTIRE supply as a single-sided liquidity position:
///      range [minUsableTick, startTick], pool initialized exactly at
///      startTick, so the position holds only tokens and every buy (ETH in)
///      moves the price down-tick through the range — i.e. up in USD terms.
///
/// v2 changes vs v1:
///   - Anti-whale: each launched token enforces a max buy of `maxBuyBps`
///     (default 0.77% of supply) per transfer out of the pool. The owner can
///     lift or re-enable limits per token (`setTokenLimits`) and manage
///     exemptions (`setTokenExempt`).
///   - Emergency exit: the owner can remove a token's LP position partially
///     or fully (`removeLiquidity`) and receive the underlying ETH + tokens.
///     NOTE: this makes liquidity owner-custodied rather than locked — it is
///     a safety hatch, and holders can see it exists on-chain.
///   - ETH taxes: every pool is created with the MachineFeeHook attached,
///     charging buy/sell taxes purely in ETH (default 10%/15%), lowerable
///     but never raisable via the hook's `lowerTaxes`.
contract MachineFunLauncher is IUnlockCallback, Ownable {
    // ---------------------------------------------------------------- errors
    error NotPoolManager();
    error WrongLaunchFee();
    error InvalidStartTick();
    error UnknownToken();
    error UnexpectedEthOwed();
    error FeeShareTooHigh();
    error ZeroAddress();
    error EthTransferFailed();
    error NoLiquidity();

    // ---------------------------------------------------------------- events
    event TokenLaunched(
        address indexed token,
        address indexed creator,
        PoolId indexed poolId,
        int24 startTick,
        uint128 liquidity,
        uint256 tokensInPool
    );
    event FeesCollected(address indexed token, uint256 ethAmount, uint256 tokenAmount);
    event LiquidityRemoved(
        address indexed token,
        uint128 liquidity,
        uint256 ethAmount,
        uint256 tokenAmount,
        address recipient
    );
    event DefaultStartTickSet(int24 startTick);
    event CreatorFeeBpsSet(uint16 bps);
    event MaxBuyBpsSet(uint16 bps);
    event ProtocolFeeRecipientSet(address recipient);
    event LaunchFeeSet(uint256 fee);

    // ------------------------------------------------------------- constants
    /// @notice Fixed supply of every launched token (1 billion, 18 decimals).
    uint256 public constant TOKEN_SUPPLY = 1_000_000_000e18;

    /// @notice LP fee of every pool: 1% (units of hundredths of a bip).
    uint24 public constant LP_FEE = 10_000;

    /// @notice Tick spacing of every pool.
    int24 public constant TICK_SPACING = 200;

    /// @notice Uniswap v4 PoolManager (canonical singleton on each chain).
    IPoolManager public immutable poolManager;

    /// @notice ETH tax hook attached to every launched pool.
    MachineFeeHook public immutable feeHook;

    // ---------------------------------------------------------------- config
    /// @notice Default upper tick of new positions = starting price.
    /// @dev 190600 ≈ 1.893e8 tokens/ETH ≈ $10k FDV at ETH $1,900.
    int24 public defaultStartTick = 190_600;

    /// @notice Share of collected LP fees paid to the token creator (bps).
    uint16 public creatorFeeBps = 5_000; // 50%

    /// @notice Max buy per swap for newly launched tokens (bps of supply).
    uint16 public maxBuyBps = 77; // 0.77%

    /// @notice Initial ETH taxes for newly launched pools (bps).
    uint16 public buyTaxBps = 1_000; // 10%
    uint16 public sellTaxBps = 1_500; // 15%

    /// @notice Receives the protocol's share of LP fees and launch fees.
    address public protocolFeeRecipient;

    /// @notice Optional flat ETH fee charged per launch (0 by default).
    uint256 public launchFee;

    // ----------------------------------------------------------------- state
    struct TokenInfo {
        address creator;
        int24 tickLower;
        int24 tickUpper;
        uint128 liquidity;
        PoolKey poolKey;
    }

    /// @notice Launch info for every token deployed through this contract.
    mapping(address token => TokenInfo) internal _tokenInfo;

    /// @notice All tokens ever launched (for indexers/frontends).
    address[] public allTokens;

    // -------------------------------------------------------------- callback
    enum Action {
        ADD_LIQUIDITY,
        COLLECT_FEES,
        REMOVE_LIQUIDITY
    }

    struct CallbackData {
        Action action;
        address token;
        PoolKey key;
        int24 tickLower;
        int24 tickUpper;
        uint128 liquidity;
        address recipient;
    }

    constructor(IPoolManager poolManager_, address protocolFeeRecipient_, MachineFeeHook feeHook_)
        Ownable(msg.sender)
    {
        if (
            address(poolManager_) == address(0) || protocolFeeRecipient_ == address(0)
                || address(feeHook_) == address(0)
        ) revert ZeroAddress();
        poolManager = poolManager_;
        protocolFeeRecipient = protocolFeeRecipient_;
        feeHook = feeHook_;
    }

    // ---------------------------------------------------------------- launch
    /// @notice Deploy a token and launch it on Uniswap v4 in one transaction.
    /// @param name Token name.
    /// @param symbol Token symbol.
    /// @param metadataURI Off-chain metadata URI (image/description), may be "".
    /// @param startTickOverride Custom starting tick (multiple of TICK_SPACING),
    ///        or 0 to use `defaultStartTick`. Frontends should compute this
    ///        from the live ETH price to hit the $10k target exactly.
    /// @return token The launched token address.
    /// @return poolId The Uniswap v4 pool id.
    function launch(
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        int24 startTickOverride
    ) external payable returns (address token, PoolId poolId) {
        if (msg.value != launchFee) revert WrongLaunchFee();

        int24 startTick = startTickOverride == 0 ? defaultStartTick : startTickOverride;
        int24 tickLower = TickMath.minUsableTick(TICK_SPACING);
        if (
            startTick % TICK_SPACING != 0 || startTick <= tickLower
                || startTick > TickMath.maxUsableTick(TICK_SPACING)
        ) revert InvalidStartTick();

        MachineToken t = new MachineToken(
            name, symbol, metadataURI, TOKEN_SUPPLY, address(poolManager), maxBuyBps
        );
        token = address(t);

        // Fee payouts flow straight from the PoolManager and must not trip
        // the max-buy check.
        t.setExempt(msg.sender, true);
        t.setExempt(protocolFeeRecipient, true);

        PoolKey memory key = PoolKey({
            currency0: Currency.wrap(address(0)), // native ETH — always currency0
            currency1: Currency.wrap(token),
            fee: LP_FEE,
            tickSpacing: TICK_SPACING,
            hooks: IHooks(address(feeHook))
        });
        poolId = key.toId();

        // Arm the ETH taxes before the pool can trade.
        feeHook.registerPool(poolId, buyTaxBps, sellTaxBps);

        // Initialize exactly at the top of the range: position is 100% token.
        poolManager.initialize(key, TickMath.getSqrtPriceAtTick(startTick));

        // Liquidity that a [tickLower, startTick] position needs to hold the
        // full supply as token1: L = amount1 * Q96 / (sqrtUpper - sqrtLower).
        uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
        uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(startTick);
        uint128 liquidity =
            uint128(FullMath.mulDiv(TOKEN_SUPPLY, FixedPoint96.Q96, sqrtUpper - sqrtLower));

        poolManager.unlock(
            abi.encode(
                CallbackData({
                    action: Action.ADD_LIQUIDITY,
                    token: token,
                    key: key,
                    tickLower: tickLower,
                    tickUpper: startTick,
                    liquidity: liquidity,
                    recipient: address(0)
                })
            )
        );

        _tokenInfo[token] = TokenInfo({
            creator: msg.sender,
            tickLower: tickLower,
            tickUpper: startTick,
            liquidity: liquidity,
            poolKey: key
        });
        allTokens.push(token);

        // Rounding leaves at most a few thousand wei of token behind — burn it
        // so this contract never holds a spendable balance of launched tokens.
        uint256 dust = t.balanceOf(address(this));
        if (dust > 0) t.transfer(address(0xdead), dust);

        emit TokenLaunched(token, msg.sender, poolId, startTick, liquidity, TOKEN_SUPPLY - dust);
    }

    // ------------------------------------------------------------------ fees
    /// @notice Collect the LP fees a token's position has earned and pay them
    ///         out (creatorFeeBps to the creator, remainder to the protocol).
    ///         Permissionless: anyone may trigger a collection.
    function collectFees(address token) external {
        TokenInfo memory info = _tokenInfo[token];
        if (info.creator == address(0)) revert UnknownToken();

        poolManager.unlock(
            abi.encode(
                CallbackData({
                    action: Action.COLLECT_FEES,
                    token: token,
                    key: info.poolKey,
                    tickLower: info.tickLower,
                    tickUpper: info.tickUpper,
                    liquidity: 0,
                    recipient: address(0)
                })
            )
        );
    }

    // ------------------------------------------------------- emergency exit
    /// @notice Remove some or all of a token's LP position and send the
    ///         underlying ETH + tokens (plus any accrued fees) to `recipient`.
    ///         Owner only. Pass type(uint128).max to remove everything.
    function removeLiquidity(address token, uint128 liquidity, address recipient)
        external
        onlyOwner
    {
        if (recipient == address(0)) revert ZeroAddress();
        TokenInfo storage info = _tokenInfo[token];
        if (info.creator == address(0)) revert UnknownToken();
        if (info.liquidity == 0) revert NoLiquidity();

        if (liquidity > info.liquidity) liquidity = info.liquidity;
        info.liquidity -= liquidity;

        // The withdrawal itself is a large transfer out of the PoolManager.
        MachineToken(token).setExempt(recipient, true);

        poolManager.unlock(
            abi.encode(
                CallbackData({
                    action: Action.REMOVE_LIQUIDITY,
                    token: token,
                    key: info.poolKey,
                    tickLower: info.tickLower,
                    tickUpper: info.tickUpper,
                    liquidity: liquidity,
                    recipient: recipient
                })
            )
        );
    }

    /// @inheritdoc IUnlockCallback
    function unlockCallback(bytes calldata rawData) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert NotPoolManager();
        CallbackData memory data = abi.decode(rawData, (CallbackData));

        if (data.action == Action.ADD_LIQUIDITY) {
            (BalanceDelta delta,) = poolManager.modifyLiquidity(
                data.key,
                ModifyLiquidityParams({
                    tickLower: data.tickLower,
                    tickUpper: data.tickUpper,
                    liquidityDelta: int256(uint256(data.liquidity)),
                    salt: bytes32(0)
                }),
                ""
            );
            // Initialized at tickUpper => the position needs no ETH at all.
            if (delta.amount0() != 0) revert UnexpectedEthOwed();

            // Pay the tokens we owe the pool.
            uint256 owed = uint256(uint128(-delta.amount1()));
            poolManager.sync(data.key.currency1);
            IERC20(data.token).transfer(address(poolManager), owed);
            poolManager.settle();
        } else if (data.action == Action.COLLECT_FEES) {
            // liquidityDelta == 0 pokes the position; the returned delta is
            // exactly the fees accrued since the last collection.
            (BalanceDelta delta,) = poolManager.modifyLiquidity(
                data.key,
                ModifyLiquidityParams({
                    tickLower: data.tickLower,
                    tickUpper: data.tickUpper,
                    liquidityDelta: 0,
                    salt: bytes32(0)
                }),
                ""
            );

            address creator = _tokenInfo[data.token].creator;
            uint256 eth = uint256(uint128(delta.amount0()));
            uint256 tok = uint256(uint128(delta.amount1()));
            _payOut(data.key.currency0, eth, creator);
            _payOut(data.key.currency1, tok, creator);
            emit FeesCollected(data.token, eth, tok);
        } else {
            // REMOVE_LIQUIDITY: withdraw principal (+ any accrued fees) to
            // the owner-chosen recipient.
            (BalanceDelta delta,) = poolManager.modifyLiquidity(
                data.key,
                ModifyLiquidityParams({
                    tickLower: data.tickLower,
                    tickUpper: data.tickUpper,
                    liquidityDelta: -int256(uint256(data.liquidity)),
                    salt: bytes32(0)
                }),
                ""
            );

            uint256 eth = uint256(uint128(delta.amount0()));
            uint256 tok = uint256(uint128(delta.amount1()));
            if (eth > 0) poolManager.take(data.key.currency0, data.recipient, eth);
            if (tok > 0) poolManager.take(data.key.currency1, data.recipient, tok);
            emit LiquidityRemoved(data.token, data.liquidity, eth, tok, data.recipient);
        }
        return "";
    }

    function _payOut(Currency currency, uint256 amount, address creator) internal {
        if (amount == 0) return;
        uint256 creatorShare = (amount * creatorFeeBps) / 10_000;
        if (creatorShare > 0) poolManager.take(currency, creator, creatorShare);
        if (amount - creatorShare > 0) {
            poolManager.take(currency, protocolFeeRecipient, amount - creatorShare);
        }
    }

    // ----------------------------------------------------------------- views
    function tokenInfo(address token)
        external
        view
        returns (
            address creator,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            PoolKey memory poolKey
        )
    {
        TokenInfo memory info = _tokenInfo[token];
        return (info.creator, info.tickLower, info.tickUpper, info.liquidity, info.poolKey);
    }

    function allTokensLength() external view returns (uint256) {
        return allTokens.length;
    }

    // ----------------------------------------------------------------- admin
    /// @notice Enable/disable a token's max-buy limit.
    function setTokenLimits(address token, bool enabled) external onlyOwner {
        if (_tokenInfo[token].creator == address(0)) revert UnknownToken();
        MachineToken(token).setLimitsEnabled(enabled);
    }

    /// @notice Manage a token's max-buy exemption list.
    function setTokenExempt(address token, address account, bool exempt) external onlyOwner {
        if (_tokenInfo[token].creator == address(0)) revert UnknownToken();
        MachineToken(token).setExempt(account, exempt);
    }

    function setDefaultStartTick(int24 startTick) external onlyOwner {
        if (
            startTick % TICK_SPACING != 0 || startTick <= TickMath.minUsableTick(TICK_SPACING)
                || startTick > TickMath.maxUsableTick(TICK_SPACING)
        ) revert InvalidStartTick();
        defaultStartTick = startTick;
        emit DefaultStartTickSet(startTick);
    }

    function setCreatorFeeBps(uint16 bps) external onlyOwner {
        if (bps > 10_000) revert FeeShareTooHigh();
        creatorFeeBps = bps;
        emit CreatorFeeBpsSet(bps);
    }

    /// @notice Max buy (bps of supply) applied to FUTURE launches.
    function setMaxBuyBps(uint16 bps) external onlyOwner {
        if (bps > 10_000) revert FeeShareTooHigh();
        maxBuyBps = bps;
        emit MaxBuyBpsSet(bps);
    }

    /// @notice Initial ETH taxes applied to FUTURE launches (hard cap 25%).
    function setInitialTaxes(uint16 buyBps, uint16 sellBps) external onlyOwner {
        if (buyBps > 2_500 || sellBps > 2_500) revert FeeShareTooHigh();
        buyTaxBps = buyBps;
        sellTaxBps = sellBps;
    }

    function setProtocolFeeRecipient(address recipient) external onlyOwner {
        if (recipient == address(0)) revert ZeroAddress();
        protocolFeeRecipient = recipient;
        emit ProtocolFeeRecipientSet(recipient);
    }

    function setLaunchFee(uint256 fee) external onlyOwner {
        launchFee = fee;
        emit LaunchFeeSet(fee);
    }

    /// @notice Sweep accumulated launch fees (ETH) to the protocol recipient.
    function withdrawLaunchFees() external {
        (bool ok,) = protocolFeeRecipient.call{value: address(this).balance}("");
        if (!ok) revert EthTransferFailed();
    }
}
Chain explorer7478msChain node87ms