{"language":"Solidity","sources":{"src/LazyFactory.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {LazyCurve} from \"./LazyCurve.sol\";\nimport {LazyToken} from \"./LazyToken.sol\";\n\ninterface ILazyFeeVault {\n    function register(\n        address token,\n        address curve,\n        address creator,\n        uint16 creatorBps,\n        uint16 holdersBps,\n        uint16 burnBps,\n        address[] calldata excludedAccounts\n    ) external;\n}\n\ninterface ILazyDirect {\n    struct LaunchParams {\n        string name;\n        string symbol;\n        string metadataURI;\n        uint160 sqrtPriceX96;\n        int24 tickLower;\n        int24 tickUpper;\n        uint160 sqrtLowerX96;\n        uint160 sqrtUpperX96;\n        uint16 creatorSplitBps;\n        uint16 holdersSplitBps;\n        uint16 burnSplitBps;\n        uint256 devBuy;\n        uint256 devMinOut;\n    }\n\n    function launchFor(address creator, address token, LaunchParams calldata p)\n        external\n        payable\n        returns (address token_, uint256 tokenId);\n}\n\n/// @notice Entry point users call from the LazyMemes site to launch a meme coin on Arc.\ncontract LazyFactory {\n    uint256 public constant SUPPLY = 1_000_000_000 ether; // 1B tokens\n    /// Every LazyMemes token address must end with these 4 hex chars.\n    uint16 public constant VANITY_SUFFIX = 0x4477;\n    /// @dev Immutable per deployment. Production: $4,828.5 virtual quote against a\n    ///      1,073,000,000 virtual token reserve (pump.fun-style phantom float) and a\n    ///      $13,680.75 raise — that combination opens the Uniswap pool at exactly the\n    ///      last curve price. A separate low-threshold instance is used for staging tests.\n    uint256 public immutable VIRTUAL_QUOTE;\n    uint256 public immutable VIRTUAL_TOKEN;\n    uint256 public immutable RAISE_TARGET;\n\n\n    address public platform;\n    address public directLauncher;\n    /// @notice Fixed destination for graduated curve liquidity (the graduator helper).\n    address public graduator;\n    /// @notice Incident switch: blocks new launches only, never touches user funds.\n    bool public paused;\n    address public immutable feeVault;\n    uint256 public launchFee = 0.1 ether; // 0.1 USDC on Arc\n\n    address[] public curves;\n    mapping(address => address) public curveOfToken;\n    address[] public directTokens;\n    mapping(address => uint256) public directLaunchOfToken;\n\n    event Launched(\n        address indexed creator,\n        address indexed token,\n        address indexed curve,\n        string name,\n        string symbol,\n        string metadataURI,\n        uint16 creatorSplitBps,\n        uint16 holdersSplitBps,\n        uint16 burnSplitBps\n    );\n    event LaunchedDirect(\n        address indexed creator,\n        address indexed token,\n        uint256 tokenId,\n        string name,\n        string symbol,\n        string metadataURI\n    );\n    event PlatformUpdated(address platform);\n    event LaunchFeeUpdated(uint256 fee);\n    event DirectLauncherUpdated(address launcher);\n    event GraduatorUpdated(address graduator);\n    event PausedUpdated(bool paused);\n\n    modifier onlyPlatform() {\n        require(msg.sender == platform, \"NOT_PLATFORM\");\n        _;\n    }\n\n    constructor(\n        address _platform,\n        address _feeVault,\n        uint256 _virtualQuote,\n        uint256 _virtualToken,\n        uint256 _raiseTarget\n    ) {\n        require(_platform != address(0), \"ZERO_PLATFORM\");\n        require(_virtualQuote > 0 && _raiseTarget > 0 && _virtualToken >= SUPPLY, \"BAD_CURVE\");\n        platform = _platform;\n        feeVault = _feeVault;\n        VIRTUAL_QUOTE = _virtualQuote;\n        VIRTUAL_TOKEN = _virtualToken;\n        RAISE_TARGET = _raiseTarget;\n    }\n\n\n    function curvesLength() external view returns (uint256) {\n        return curves.length;\n    }\n\n    /// @notice Deterministic address of the token for a given salt — used to mine the 4477 suffix.\n    function predictToken(\n        string calldata name,\n        string calldata symbol,\n        string calldata metadataURI,\n        address creator,\n        bytes32 salt\n    ) public view returns (address) {\n        bytes32 initHash = keccak256(\n            abi.encodePacked(\n                type(LazyToken).creationCode,\n                abi.encode(name, symbol, metadataURI, creator, address(this), SUPPLY, feeVault)\n            )\n        );\n        return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initHash)))));\n    }\n\n    function _deployToken(\n        string calldata name,\n        string calldata symbol,\n        string calldata metadataURI,\n        bytes32 salt\n    ) internal returns (LazyToken token) {\n        token = new LazyToken{salt: salt}(name, symbol, metadataURI, msg.sender, address(this), SUPPLY, feeVault);\n        require(uint16(uint160(address(token))) == VANITY_SUFFIX, \"VANITY\");\n    }\n\n    /// @param initialBuy amount of native quote (after the launch fee) to spend on the first buy\n    /// @param salt CREATE2 salt mined off-chain so the token address ends with 4477\n    function launch(\n        string calldata name,\n        string calldata symbol,\n        string calldata metadataURI,\n        uint256 initialBuy,\n        uint16 creatorSplitBps,\n        uint16 holdersSplitBps,\n        uint16 burnSplitBps,\n        bytes32 salt\n    ) external payable returns (address tokenAddr, address curveAddr) {\n        require(!paused, \"PAUSED\");\n        require(graduator != address(0), \"NO_GRADUATOR\");\n        require(bytes(name).length > 0 && bytes(symbol).length > 0, \"META\");\n        require(msg.value >= launchFee + initialBuy, \"FEE\");\n\n        // Everything above this baseline belongs to the caller, including any refund\n        // the curve sends back when the initial buy is larger than the curve can fill.\n        uint256 balBefore = address(this).balance - msg.value;\n\n\n        LazyToken token = _deployToken(name, symbol, metadataURI, salt);\n\n        LazyCurve curve = new LazyCurve(\n            platform,\n            msg.sender,\n            VIRTUAL_QUOTE,\n            VIRTUAL_TOKEN,\n            RAISE_TARGET,\n\n            address(token),\n            SUPPLY,\n            creatorSplitBps,\n            holdersSplitBps,\n            burnSplitBps,\n            feeVault,\n            graduator\n        );\n\n        address[] memory skip = new address[](1);\n        skip[0] = address(curve);\n        ILazyFeeVault(feeVault).register(\n            address(token), address(curve), msg.sender,\n            creatorSplitBps, holdersSplitBps, burnSplitBps, skip\n        );\n\n        token.transfer(address(curve), SUPPLY);\n\n        (bool ok, ) = platform.call{value: launchFee}(\"\");\n        require(ok, \"LAUNCH_FEE\");\n\n        tokenAddr = address(token);\n        curveAddr = address(curve);\n        curves.push(curveAddr);\n        curveOfToken[tokenAddr] = curveAddr;\n\n        emit Launched(\n            msg.sender, tokenAddr, curveAddr, name, symbol, metadataURI,\n            creatorSplitBps, holdersSplitBps, burnSplitBps\n        );\n\n        if (initialBuy > 0) {\n            curve.buy{value: initialBuy}(0);\n            uint256 bought = LazyToken(tokenAddr).balanceOf(address(this));\n            if (bought > 0) LazyToken(tokenAddr).transfer(msg.sender, bought);\n        }\n\n        // Refund every unspent wei of the caller's value, never any stray balance held here.\n        uint256 dust = address(this).balance > balBefore ? address(this).balance - balBefore : 0;\n        if (dust > 0) {\n            (bool r, ) = msg.sender.call{value: dust}(\"\");\n            require(r, \"REFUND\");\n        }\n\n    }\n\n    /// @notice Direct-to-market launch, routed through this factory so every LazyMemes\n    ///         token (curve or direct) is attributable to the same factory address.\n    function launchDirect(ILazyDirect.LaunchParams calldata p, bytes32 salt)\n        external\n        payable\n        returns (address tokenAddr, uint256 tokenId)\n    {\n        require(!paused, \"PAUSED\");\n        require(directLauncher != address(0), \"NO_DIRECT\");\n        require(bytes(p.name).length > 0 && bytes(p.symbol).length > 0, \"META\");\n        require(msg.value >= launchFee + p.devBuy, \"FEE\");\n\n        LazyToken token = _deployToken(p.name, p.symbol, p.metadataURI, salt);\n\n        address[] memory skip = new address[](1);\n        skip[0] = directLauncher;\n        ILazyFeeVault(feeVault).register(\n            address(token), address(0), msg.sender,\n            p.creatorSplitBps, p.holdersSplitBps, p.burnSplitBps, skip\n        );\n\n        token.transfer(directLauncher, SUPPLY);\n\n        (tokenAddr, tokenId) = ILazyDirect(directLauncher).launchFor{value: launchFee + p.devBuy}(msg.sender, address(token), p);\n\n        directTokens.push(tokenAddr);\n        directLaunchOfToken[tokenAddr] = tokenId;\n\n        emit LaunchedDirect(msg.sender, tokenAddr, tokenId, p.name, p.symbol, p.metadataURI);\n\n        uint256 dust = msg.value - launchFee - p.devBuy;\n        if (dust > address(this).balance) dust = address(this).balance;\n        if (dust > 0) {\n            (bool r, ) = msg.sender.call{value: dust}(\"\");\n            require(r, \"REFUND\");\n        }\n    }\n\n    function directTokensLength() external view returns (uint256) {\n        return directTokens.length;\n    }\n\n    function setGraduator(address _graduator) external onlyPlatform {\n        graduator = _graduator;\n        emit GraduatorUpdated(_graduator);\n    }\n\n    function setPaused(bool _paused) external onlyPlatform {\n        paused = _paused;\n        emit PausedUpdated(_paused);\n    }\n\n    function setDirectLauncher(address _launcher) external onlyPlatform {\n        directLauncher = _launcher;\n        emit DirectLauncherUpdated(_launcher);\n    }\n\n    function setPlatform(address _platform) external onlyPlatform {\n        require(_platform != address(0), \"ZERO\");\n        platform = _platform;\n        emit PlatformUpdated(_platform);\n    }\n\n    function setLaunchFee(uint256 _fee) external onlyPlatform {\n        launchFee = _fee;\n        emit LaunchFeeUpdated(_fee);\n    }\n\n    receive() external payable {}\n}\n"},"src/LazyCurve.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {LazyToken} from \"./LazyToken.sol\";\n\ninterface IFeeVault {\n    function deposit(address token) external payable;\n}\n\ninterface IGraduatorMin {\n    function graduate(address curve) external returns (uint256 tokenId);\n}\n\n/// @notice Constant-product bonding curve priced in the chain's native currency (USDC on Arc).\n///         Virtual quote reserve gives a $4,500 starting market cap on a 1B supply.\n///         Trading fee: 1% total, split 0.8% creator / 0.2% platform.\n///         Graduation once `raiseTarget` of net quote has entered the curve.\ncontract LazyCurve {\n    uint256 public constant FEE_BPS = 100; // 1%\n    uint256 public constant CREATOR_BPS = 80; // 0.8%\n    uint256 public constant GRADUATION_FEE_BPS = 100; // 1% of pool at graduation\n    address public constant DEAD = 0x000000000000000000000000000000000000dEaD;\n\n    /// Split of the creator's 0.8% share, in bps of that share (sums to 10_000).\n    uint16 public immutable creatorSplitBps;\n    uint16 public immutable holdersSplitBps;\n    uint16 public immutable burnSplitBps;\n\n    /// Reward vault holding the holders + burn slices until settlement.\n    address public immutable feeVault;\n\n    address public immutable factory;\n    address public immutable platform;\n    address public immutable creator;\n    LazyToken public immutable token;\n\n    uint256 public immutable virtualQuote; // starting virtual quote reserve\n    uint256 public immutable raiseTarget; // net quote to raise before graduation\n    /// @notice Virtual token reserve the curve prices against. It is larger than the real\n    ///         supply (pump.fun style): the extra tokens never exist, which is what lets the\n    ///         Uniswap pool open at exactly the last curve price instead of below it.\n    uint256 public immutable virtualToken;\n    /// @notice Real tokens sellable on the curve. Everything above this stays for the pool.\n    uint256 public immutable sellCap;\n\n    uint256 public quoteReserve; // virtual + real\n    uint256 public tokenReserve; // virtual token reserve\n    uint256 public tokensSold; // real tokens handed to buyers\n    uint256 public quoteRaised; // real quote collected into the curve\n\n\n    bool public graduated;\n    bool public migrated;\n\n    /// Payouts that could not be pushed (recipient reverted); withdrawable any time.\n    mapping(address => uint256) public owed;\n    uint256 private _totalOwed;\n\n    /// @notice The only address graduated liquidity can ever be migrated to,\n    ///         fixed at launch so a compromised platform key cannot redirect it.\n    address public immutable migrateTarget;\n\n    uint256 private _lock = 1;\n\n    modifier nonReentrant() {\n        require(_lock == 1, \"REENTRANT\");\n        _lock = 2;\n        _;\n        _lock = 1;\n    }\n\n    function totalOwed() public view returns (uint256) {\n        return _totalOwed;\n    }\n\n    event Buy(address indexed buyer, uint256 quoteIn, uint256 tokensOut, uint256 fee);\n    event Sell(address indexed seller, uint256 tokensIn, uint256 quoteOut, uint256 fee);\n    event Graduated(uint256 quoteForLiquidity, uint256 tokensForLiquidity, uint256 fee);\n    event Migrated(address indexed target, uint256 quoteAmount, uint256 tokenAmount);\n    event FeeSplit(uint256 toCreator, uint256 toHolders, uint256 burned, uint256 toPlatform);\n    event PayoutDeferred(address indexed to, uint256 amount);\n    event AutoMigrateAttempted(address indexed target);\n    event AutoMigrated(address indexed target);\n    event AutoMigrateFailed(address indexed target);\n\n\n    /// @param _token pre-deployed vanity LazyToken; the factory transfers the full supply here.\n    constructor(\n        address _platform,\n        address _creator,\n        uint256 _virtualQuote,\n        uint256 _virtualToken,\n        uint256 _raiseTarget,\n        address _token,\n        uint256 _supply,\n        uint16 _creatorSplitBps,\n        uint16 _holdersSplitBps,\n        uint16 _burnSplitBps,\n        address _feeVault,\n        address _migrateTarget\n    ) {\n        require(_platform != address(0), \"ZERO_PLATFORM\");\n        require(\n            uint256(_creatorSplitBps) + uint256(_holdersSplitBps) + uint256(_burnSplitBps) == 10_000,\n            \"SPLIT\"\n        );\n        creatorSplitBps = _creatorSplitBps;\n        holdersSplitBps = _holdersSplitBps;\n        burnSplitBps = _burnSplitBps;\n        feeVault = _feeVault;\n        migrateTarget = _migrateTarget;\n        factory = msg.sender;\n        platform = _platform;\n        creator = _creator;\n        virtualQuote = _virtualQuote;\n        raiseTarget = _raiseTarget;\n        virtualToken = _virtualToken;\n        token = LazyToken(_token);\n        quoteReserve = _virtualQuote;\n        tokenReserve = _virtualToken;\n\n        // Tokens that will have been sold once the raise target is met, from the\n        // constant-product curve itself. The rest of the real supply is the pool float.\n        uint256 endReserve = (_virtualQuote * _virtualToken) / (_virtualQuote + _raiseTarget);\n        uint256 cap = _virtualToken - endReserve;\n        require(cap > 0 && cap <= _supply, \"CURVE_PARAMS\");\n        sellCap = cap;\n    }\n\n\n    function marketCap() external view returns (uint256) {\n        return (quoteReserve * token.totalSupply()) / tokenReserve;\n    }\n\n    function quoteBuy(uint256 quoteIn) public view returns (uint256 tokensOut, uint256 fee) {\n        fee = (quoteIn * FEE_BPS) / 10_000;\n        uint256 net = quoteIn - fee;\n        tokensOut = (tokenReserve * net) / (quoteReserve + net);\n    }\n\n    function quoteSell(uint256 tokensIn) public view returns (uint256 quoteOut, uint256 fee) {\n        uint256 gross = (quoteReserve * tokensIn) / (tokenReserve + tokensIn);\n        fee = (gross * FEE_BPS) / 10_000;\n        quoteOut = gross - fee;\n    }\n\n    function _ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a == 0 ? 0 : (a - 1) / b + 1;\n    }\n\n    function buy(uint256 minTokensOut) external payable nonReentrant {\n        require(!graduated, \"GRADUATED\");\n        require(msg.value > 0, \"NO_VALUE\");\n        uint256 spend = msg.value;\n        (uint256 tokensOut, uint256 fee) = quoteBuy(spend);\n\n        // Never sell past the curve's share of the supply: the remainder is the\n        // pool float. An oversized buy is filled up to the cap and the rest refunded.\n        uint256 remaining = sellCap - tokensSold;\n        uint256 refund;\n        if (tokensOut > remaining) {\n            tokensOut = remaining;\n            uint256 netNeeded = _ceilDiv(quoteReserve * remaining, tokenReserve - remaining);\n            uint256 gross = _ceilDiv(netNeeded * 10_000, 10_000 - FEE_BPS);\n            if (gross > spend) gross = spend;\n            refund = spend - gross;\n            spend = gross;\n            fee = (spend * FEE_BPS) / 10_000;\n        }\n        require(tokensOut >= minTokensOut && tokensOut > 0, \"SLIPPAGE\");\n\n        uint256 net = spend - fee;\n        quoteReserve += net;\n        tokenReserve -= tokensOut;\n        tokensSold += tokensOut;\n        quoteRaised += net;\n\n        _payFees(fee);\n        require(token.transfer(msg.sender, tokensOut), \"TRANSFER_OUT\");\n        emit Buy(msg.sender, spend, tokensOut, fee);\n\n        if (refund > 0) {\n            (bool r, ) = msg.sender.call{value: refund}(\"\");\n            require(r, \"REFUND\");\n        }\n\n        if (!graduated && quoteRaised >= raiseTarget) _graduate();\n    }\n\n    function sell(uint256 tokensIn, uint256 minQuoteOut) external nonReentrant {\n        require(!graduated, \"GRADUATED\");\n        require(tokensIn > 0, \"NO_TOKENS\");\n        (uint256 quoteOut, uint256 fee) = quoteSell(tokensIn);\n        require(quoteOut >= minQuoteOut && quoteOut > 0, \"SLIPPAGE\");\n\n        require(token.transferFrom(msg.sender, address(this), tokensIn), \"TRANSFER_IN\");\n        quoteReserve -= (quoteOut + fee);\n        tokenReserve += tokensIn;\n        tokensSold = tokensSold > tokensIn ? tokensSold - tokensIn : 0;\n        quoteRaised = quoteRaised > (quoteOut + fee) ? quoteRaised - (quoteOut + fee) : 0;\n\n        _payFees(fee);\n        (bool ok, ) = msg.sender.call{value: quoteOut}(\"\");\n        require(ok, \"PAY_SELLER\");\n        emit Sell(msg.sender, tokensIn, quoteOut, fee);\n    }\n\n\n    /// @dev Fees are never pushed during a trade. They are credited to the\n    ///      recipient and pulled with `withdrawOwed`, so no recipient (creator,\n    ///      platform or a hostile contract) can ever make a buy or sell revert,\n    ///      and no trade pays for someone else's fallback gas.\n    function _send(address to, uint256 amount) internal {\n        if (amount == 0) return;\n        owed[to] += amount;\n        _totalOwed += amount;\n        emit PayoutDeferred(to, amount);\n    }\n\n    /// @notice Fees this address can withdraw right now.\n    function claimable(address who) external view returns (uint256) {\n        return owed[who];\n    }\n\n    /// @notice Withdraw payouts that could not be pushed at trade time.\n    function withdrawOwed() external nonReentrant {\n        uint256 amount = owed[msg.sender];\n        require(amount > 0, \"NOTHING\");\n        owed[msg.sender] = 0;\n        _totalOwed -= amount;\n        (bool ok, ) = msg.sender.call{value: amount}(\"\");\n        require(ok, \"PAY\");\n    }\n\n    function _payFees(uint256 fee) internal {\n        if (fee == 0) return;\n        uint256 creatorCut = (fee * CREATOR_BPS) / FEE_BPS;\n\n        // Platform's fixed 0.2%.\n        _send(platform, fee - creatorCut);\n\n        // Creator's 0.8%, routed exactly as chosen on the launch page.\n        uint256 toHolders = (creatorCut * holdersSplitBps) / 10_000;\n        uint256 toBurn = (creatorCut * burnSplitBps) / 10_000;\n        uint256 toCreator = creatorCut - toHolders - toBurn;\n\n        // Holder rewards and burns pool in the vault and are swapped into the\n        // token at settlement, so holders are paid in tokens and burns really\n        // reduce supply.\n        uint256 toVault = toHolders + toBurn;\n        if (toVault > 0) IFeeVault(feeVault).deposit{value: toVault}(address(token));\n        _send(creator, toCreator);\n        emit FeeSplit(toCreator, toHolders, toBurn, fee - creatorCut);\n    }\n\n    /// @dev Gas forwarded to the graduator for the in-transaction Uniswap v4 migration.\n    uint256 internal constant MIGRATE_GAS = 4_000_000;\n\n    function _graduate() internal {\n        require(!graduated, \"GRADUATED\");\n        graduated = true;\n        // Fee is charged on the quote actually raised through trades, so an\n        // outside donation to this contract cannot inflate the platform cut.\n        uint256 bal = address(this).balance;\n        uint256 pool = quoteRaised < bal ? quoteRaised : bal;\n        uint256 fee = (pool * GRADUATION_FEE_BPS) / 10_000;\n        _send(platform, fee);\n\n        // The same 1% is taken from the token side, so removing the fee cannot\n        // move the pool's opening price away from the last curve price.\n        uint256 tokenBal = token.balanceOf(address(this));\n        uint256 tokenFee = (tokenBal * GRADUATION_FEE_BPS) / 10_000;\n        if (tokenFee > 0) token.transfer(platform, tokenFee);\n\n        emit Graduated(pool - fee, tokenBal - tokenFee, fee);\n\n\n        // Migrate to Uniswap v4 in the very same transaction that crossed the\n        // target, so liquidity is live the moment the curve closes and nothing\n        // depends on an off-chain job. Wrapped in try/catch (and gas-capped) so\n        // the buyer's trade can never fail because of the migration step; the\n        // platform keeper can still finish it later if this call runs out of gas.\n        emit AutoMigrateAttempted(migrateTarget);\n        uint256 codeSize;\n        address target = migrateTarget;\n        assembly { codeSize := extcodesize(target) }\n        if (codeSize > 0) {\n            // Demand real headroom before trying. Without this, wallet gas\n            // estimation settles on the cheap \"migration failed\" branch and the\n            // real transaction then never has enough gas to migrate.\n            require(gasleft() >= MIGRATE_GAS + (MIGRATE_GAS / 32) + 150_000, \"GRAD_GAS\");\n            (bool ok, ) = target.call{gas: MIGRATE_GAS}(\n                abi.encodeWithSelector(IGraduatorMin.graduate.selector, address(this))\n            );\n            if (ok && migrated) emit AutoMigrated(target);\n            else emit AutoMigrateFailed(target);\n        } else {\n            emit AutoMigrateFailed(target);\n        }\n    }\n\n    /// @notice Moves graduated liquidity (quote + remaining tokens) to the DEX router/helper.\n    /// @dev Callable by the platform or by the migrate target itself, so the graduator can\n    ///      pull liquidity inside the same transaction that seeds the pool and measure the\n    ///      exact amounts received instead of trusting its own balance.\n    /// @dev Deliberately not `nonReentrant`: it is called back by the graduator from\n    ///      inside the graduating buy. Safety comes from the one-shot `migrated` flag\n    ///      (set before any external call) and the fixed caller/target checks.\n    function migrate(address target) external {\n        require(msg.sender == platform || msg.sender == migrateTarget, \"NOT_PLATFORM\");\n        require(graduated && !migrated, \"NOT_READY\");\n        require(target != address(0) && target == migrateTarget, \"TARGET\");\n        migrated = true;\n        uint256 bal = address(this).balance;\n        uint256 reserved = totalOwed();\n        uint256 quoteAmount = bal > reserved ? bal - reserved : 0;\n        // Real tokens held here (supply minus what buyers took, minus the graduation\n        // fee slice) — not the virtual reserve, which includes phantom tokens.\n        uint256 tokenAmount = token.balanceOf(address(this));\n        tokenReserve = 0;\n        require(token.transfer(target, tokenAmount), \"TOKEN_SEND\");\n\n        (bool ok, ) = target.call{value: quoteAmount}(\"\");\n        require(ok, \"SEND\");\n        emit Migrated(target, quoteAmount, tokenAmount);\n    }\n\n\n    receive() external payable {}\n}\n"},"src/LazyToken.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\ninterface IFeeVaultHook {\n    function onTransfer(address from, address to, uint256 value) external;\n}\n\n/// @notice Fixed-supply ERC20 template deployed once per meme-coin launch.\n///         Entire supply is minted to the bonding curve at construction.\ncontract LazyToken {\n    string public name;\n    string public symbol;\n    uint8 public constant decimals = 18;\n    uint256 public immutable totalSupply;\n\n    address public immutable creator;\n    /// @notice Reward vault notified on every transfer so holder rewards accrue pro-rata.\n    address public immutable feeVault;\n    string public metadataURI;\n\n    uint256 private _locked = 1;\n\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    event Transfer(address indexed from, address indexed to, uint256 value);\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n\n    constructor(\n        string memory _name,\n        string memory _symbol,\n        string memory _metadataURI,\n        address _creator,\n        address _mintTo,\n        uint256 _supply,\n        address _feeVault\n    ) {\n        name = _name;\n        symbol = _symbol;\n        metadataURI = _metadataURI;\n        creator = _creator;\n        feeVault = _feeVault;\n        totalSupply = _supply;\n        balanceOf[_mintTo] = _supply;\n        emit Transfer(address(0), _mintTo, _supply);\n    }\n\n    /// @notice Alias so explorers and indexers can read the off-chain metadata JSON.\n    function tokenURI() external view returns (string memory) {\n        return metadataURI;\n    }\n\n    /// @notice Metadata is permanently frozen at deployment: nobody, including the\n    ///         creator or the platform, can ever change it.\n    function metadataFrozen() external pure returns (bool) {\n        return true;\n    }\n\n    function transfer(address to, uint256 value) external returns (bool) {\n        _transfer(msg.sender, to, value);\n        return true;\n    }\n\n    function approve(address spender, uint256 value) external returns (bool) {\n        allowance[msg.sender][spender] = value;\n        emit Approval(msg.sender, spender, value);\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 value) external returns (bool) {\n        uint256 allowed = allowance[from][msg.sender];\n        if (allowed != type(uint256).max) {\n            require(allowed >= value, \"ALLOWANCE\");\n            allowance[from][msg.sender] = allowed - value;\n        }\n        _transfer(from, to, value);\n        return true;\n    }\n\n    function _transfer(address from, address to, uint256 value) internal {\n        require(to != address(0), \"TO_ZERO\");\n        // The reward hook runs before balances change (it snapshots pre-transfer\n        // balances), so a non-reentrancy lock guarantees no callback can re-enter\n        // transfer logic while this contract is in an intermediate state.\n        require(_locked == 1, \"REENTRANCY\");\n        _locked = 2;\n        uint256 bal = balanceOf[from];\n        require(bal >= value, \"BALANCE\");\n        // Reward accounting must never be able to block a transfer: the hook is\n        // called with a bounded gas stipend and any failure is ignored.\n        if (feeVault != address(0)) {\n            try IFeeVaultHook(feeVault).onTransfer{gas: 250_000}(from, to, value) {} catch {}\n        }\n        unchecked {\n            balanceOf[from] = bal - value;\n            balanceOf[to] += value;\n        }\n        _locked = 1;\n        emit Transfer(from, to, value);\n    }\n}\n"}},"settings":{"remappings":["forge-std/=lib/forge-std/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["abi","evm.bytecode.object","evm.bytecode.sourceMap","evm.bytecode.linkReferences","evm.deployedBytecode.object","evm.deployedBytecode.sourceMap","evm.deployedBytecode.linkReferences","evm.deployedBytecode.immutableReferences","evm.methodIdentifiers","metadata"]}},"evmVersion":"cancun","viaIR":true,"libraries":{}}}
