BondingCurve

0x8ea1e9fd1ed601ce085b9c0f5d4ce6603963636e

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
11,923 bytes
ABI entries
39
30 read · 4 write
License
none

Contract information

Address
0x8ea1e9fd1ed601ce085b9c0f5d4ce6603963636e
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xf3cf3ebd60…89b8ef0bc4

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (30)

PLATFORM_FEE_BPS()uint16
POOL_FEE()uint24
TICK_SPACING()int24
creatorFeeBps()uint16
creatorFeeReceiver()address
currentPriceX96()uint160
curveK()uint256
deployer()address
dexFactory()address
ethReserve()uint256
graduated()bool
graduationProgressBps()uint16
graduationThreshold()uint256
lpLockerAddress()address
lpTokenId()uint256
marketCapWei()uint256
onERC721Received(address, address, uint256, bytes)bytes4
platformFeeReceiver()address
poolAfterGraduation()address
positionManager()address
quoteBuy(uint256)uint256, uint256, uint256, uint256, uint256
quoteSell(uint256)uint256, uint256, uint256, uint256, uint256
state()tuple
stocksVault()address
token()address
tokenReserve()uint256
vaultBps()uint16
virtualEthReserve()uint256
virtualTokenReserve()uint256
weth()address

Events (3)

BuyGraduatedSell

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract LemonToken",
        "name": "_token",
        "type": "address"
      },
      {
        "internalType": "contract IWETH",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "contract IUniswapV3Factory",
        "name": "_dexFactory",
        "type": "address"
      },
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "_positionManager",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_deployer",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_creatorFeeReceiver",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_platformFeeReceiver",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "_creatorFeeBps",
        "type": "uint16"
      },
      {
        "internalType": "uint256",
        "name": "_graduationThreshold",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_virtualTokenReserve",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_virtualEthReserve",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "_stocksVault",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "_vaultBps",
        "type": "uint16"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "buyer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethReserveAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenReserveAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "Buy",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethSeeded",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensSeeded",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "lpLocker",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "Graduated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "seller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethReserveAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenReserveAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "Sell",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "PLATFORM_FEE_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_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": "_graduateExternal",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "minTokensOut",
        "type": "uint256"
      }
    ],
    "name": "buy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creatorFeeBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creatorFeeReceiver",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "currentPriceX96",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "curveK",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deployer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "dexFactory",
    "outputs": [
      {
        "internalType": "contract IUniswapV3Factory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ethReserve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "graduate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "graduated",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "graduationProgressBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "graduationThreshold",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lpLockerAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lpTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "marketCapWei",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platformFeeReceiver",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolAfterGraduation",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "ethIn",
        "type": "uint256"
      }
    ],
    "name": "quoteBuy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "ethIntoCurve",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "platformFee",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "creatorFee",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "vaultFee",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokensIn",
        "type": "uint256"
      }
    ],
    "name": "quoteSell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "grossEthOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "platformFee",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "creatorFee",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "vaultFee",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokensIn",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minEthOut",
        "type": "uint256"
      }
    ],
    "name": "sell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "state",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint256",
            "name": "tokenReserve",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "ethReserve",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "graduationThreshold",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "graduated",
            "type": "bool"
          },
          {
            "internalType": "uint16",
            "name": "creatorFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "uint16",
            "name": "platformFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "address",
            "name": "poolAfterGraduation",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "virtualTokenReserve",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "virtualEthReserve",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "curveK",
            "type": "uint256"
          }
        ],
        "internalType": "struct BondingCurve.CurveState",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "stocksVault",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "token",
    "outputs": [
      {
        "internalType": "contract LemonToken",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenReserve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "vaultBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "virtualEthReserve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "virtualTokenReserve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "contract IWETH",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {LemonToken} from "./LemonToken.sol";
import {LpLocker} from "./LpLocker.sol";
import {IUniswapV3Factory, IUniswapV3Pool, INonfungiblePositionManager, IWETH, IERC20} from "./interfaces/IUniswap.sol";

/// @notice Pump.fun-style bonding curve for a single token.
/// - Holds ALL supply of the paired LemonToken at construction.
/// - Trades follow constant-product with virtual reserves: (x + h)(y + r) = K
///   where x = tokens remaining in curve, y = WETH raised, r/h virtual.
/// - Buys pay ETH, receive tokens minus 1% platform fee + creator fee (bps).
/// - Sells burn tokens back to curve for WETH minus same fees.
/// - When `y >= graduationThreshold`, `_graduate()` seeds a Sushi V3
///   pool with remaining tokens + raised WETH, then locks the LP NFT.
contract BondingCurve {
    // --- Immutable config --------------------------------------------------
    LemonToken   public immutable token;
    IWETH        public immutable weth;
    IUniswapV3Factory        public immutable dexFactory;
    INonfungiblePositionManager public immutable positionManager;
    address      public immutable deployer;
    address      public immutable creatorFeeReceiver;
    address      public immutable platformFeeReceiver;
    address      public immutable stocksVault;         // zero for regular launches
    uint16       public immutable creatorFeeBps;        // 0..1000 (0..10%)
    uint16       public immutable vaultBps;             // 0..500 (0..5%)  → StocksVault
    uint16       public constant  PLATFORM_FEE_BPS = 100; // 1%
    uint256      public immutable graduationThreshold;  // in WETH (wei)
    uint24       public constant  POOL_FEE = 10_000;    // 1%
    int24        public constant  TICK_SPACING = 200;

    // Curve params — (x + h)(y + r) = K
    uint256 public immutable virtualTokenReserve;  // h
    uint256 public immutable virtualEthReserve;    // r
    uint256 public immutable curveK;               // K

    // --- Mutable state -----------------------------------------------------
    uint256 public tokenReserve;   // x
    uint256 public ethReserve;     // y
    bool    public graduated;
    address public poolAfterGraduation;
    uint256 public lpTokenId;
    address public lpLockerAddress; // set at graduation; before that = address(0)

    bool private _entered;


    // Enriched indexing events — post-trade reserves and timestamps
    // travel with every trade so terminals (DEX Screener, GeckoTerminal, Photon, BullX, GMGN,
    // Axiom, etc.) can render progress bars + price without a follow-up eth_call to state().
    event Buy(
        address indexed buyer,
        uint256 ethIn,
        uint256 tokensOut,
        uint256 ethReserveAfter,
        uint256 tokenReserveAfter,
        uint256 timestamp
    );
    event Sell(
        address indexed seller,
        uint256 tokensIn,
        uint256 ethOut,
        uint256 ethReserveAfter,
        uint256 tokenReserveAfter,
        uint256 timestamp
    );
    // pool indexed so terminals can subscribe by pool address; lpLocker surfaces the LP-lock
    // trust signal that GT Score / DS Trust consume without decoding constructor traces.
    event Graduated(
        address indexed token,
        address indexed pool,
        uint256 lpTokenId,
        uint256 wethSeeded,
        uint256 tokensSeeded,
        address lpLocker,
        uint256 timestamp
    );

    modifier nonReentrant() {
        require(!_entered, "reentrant");
        _entered = true;
        _;
        _entered = false;
    }

    constructor(
        LemonToken _token,
        IWETH _weth,
        IUniswapV3Factory _dexFactory,
        INonfungiblePositionManager _positionManager,
        address _deployer,
        address _creatorFeeReceiver,
        address _platformFeeReceiver,
        uint16  _creatorFeeBps,
        uint256 _graduationThreshold,
        uint256 _virtualTokenReserve,
        uint256 _virtualEthReserve,
        address _stocksVault,
        uint16  _vaultBps
    ) {
        require(_creatorFeeBps <= 1000, "creatorFee>10%");
        require(_vaultBps <= 500, "vault>5%");
        require(_graduationThreshold > 0, "threshold=0");
        if (_stocksVault == address(0)) require(_vaultBps == 0, "vaultBps w/o vault");
        token = _token;
        weth = _weth;
        dexFactory = _dexFactory;
        positionManager = _positionManager;
        deployer = _deployer;
        creatorFeeReceiver = _creatorFeeReceiver == address(0) ? _deployer : _creatorFeeReceiver;
        platformFeeReceiver = _platformFeeReceiver;
        creatorFeeBps = _creatorFeeBps;
        graduationThreshold = _graduationThreshold;
        virtualTokenReserve = _virtualTokenReserve;
        virtualEthReserve = _virtualEthReserve;
        stocksVault = _stocksVault;
        vaultBps = _vaultBps;
        // x starts at full supply
        tokenReserve = _token.totalSupply();
        // K = (x + h)(0 + r)
        curveK = (tokenReserve + _virtualTokenReserve) * _virtualEthReserve;
    }


    // --- Views -------------------------------------------------------------

    struct CurveState {
        uint256 tokenReserve;
        uint256 ethReserve;
        uint256 graduationThreshold;
        bool    graduated;
        uint16  creatorFeeBps;
        uint16  platformFeeBps;
        address poolAfterGraduation;
        uint256 virtualTokenReserve;
        uint256 virtualEthReserve;
        uint256 curveK;
    }

    function state() external view returns (CurveState memory) {
        return CurveState({
            tokenReserve: tokenReserve,
            ethReserve: ethReserve,
            graduationThreshold: graduationThreshold,
            graduated: graduated,
            creatorFeeBps: creatorFeeBps,
            platformFeeBps: PLATFORM_FEE_BPS,
            poolAfterGraduation: poolAfterGraduation,
            virtualTokenReserve: virtualTokenReserve,
            virtualEthReserve: virtualEthReserve,
            curveK: curveK
        });
    }

    /// @notice Preview a buy: given ethIn (gross, before fees), return tokensOut.
    function quoteBuy(uint256 ethIn) public view returns (
        uint256 tokensOut,
        uint256 ethIntoCurve,
        uint256 platformFee,
        uint256 creatorFee,
        uint256 vaultFee
    ) {
        (platformFee, creatorFee, vaultFee) = _splitFees(ethIn);
        ethIntoCurve = ethIn - platformFee - creatorFee - vaultFee;
        // (x + h)(y + r) = K → new x' = K / (y + ethIn + r) − h; tokensOut = x − x'
        uint256 newY = ethReserve + ethIntoCurve;
        uint256 newXPlusH = curveK / (newY + virtualEthReserve);
        uint256 xPlusH = tokenReserve + virtualTokenReserve;
        if (newXPlusH >= xPlusH) return (0, ethIntoCurve, platformFee, creatorFee, vaultFee);
        tokensOut = xPlusH - newXPlusH;
        if (tokensOut > tokenReserve) tokensOut = tokenReserve;
    }

    /// @notice Preview a sell: given tokensIn, return net ETH out to seller.
    function quoteSell(uint256 tokensIn) public view returns (
        uint256 ethOut,
        uint256 grossEthOut,
        uint256 platformFee,
        uint256 creatorFee,
        uint256 vaultFee
    ) {
        uint256 newX = tokenReserve + tokensIn;
        uint256 newYPlusR = curveK / (newX + virtualTokenReserve);
        uint256 yPlusR = ethReserve + virtualEthReserve;
        if (newYPlusR >= yPlusR) return (0, 0, 0, 0, 0);
        grossEthOut = yPlusR - newYPlusR;
        if (grossEthOut > ethReserve) grossEthOut = ethReserve;
        (platformFee, creatorFee, vaultFee) = _splitFees(grossEthOut);
        ethOut = grossEthOut - platformFee - creatorFee - vaultFee;
    }

    function _splitFees(uint256 amount) internal view returns (uint256 platformFee, uint256 creatorFee, uint256 vaultFee) {
        platformFee = (amount * PLATFORM_FEE_BPS) / 10_000;
        creatorFee = (amount * creatorFeeBps) / 10_000;
        vaultFee = (amount * vaultBps) / 10_000;
    }


    // --- Terminal-friendly views ------------------------------------------
    // Cheap eth_call helpers so indexers (DEX Screener launchpad module,
    // GeckoTerminal, Photon, GMGN, Axiom, etc.) can render progress / price /
    // marketcap without decoding events.

    /// @notice Graduation progress in basis points (0..10000).
    /// Uses both ETH-raised model (our threshold) and tokens-sold
    /// model, whichever is further along, so generic parsers work.
    function graduationProgressBps() external view returns (uint16) {
        if (graduated) return 10_000;
        uint256 ethBps = graduationThreshold == 0 ? 0 : (ethReserve * 10_000) / graduationThreshold;
        uint256 totalSupply_ = token.totalSupply();
        uint256 sold = totalSupply_ > tokenReserve ? totalSupply_ - tokenReserve : 0;
        uint256 tokenBps = totalSupply_ == 0 ? 0 : (sold * 10_000) / totalSupply_;
        uint256 best = ethBps > tokenBps ? ethBps : tokenBps;
        if (best > 10_000) best = 10_000;
        return uint16(best);
    }

    /// @notice Marginal price on the curve as wei-per-token in Q64.96 format,
    /// same shape Uniswap V3 uses so terminals can reuse existing sqrtPriceX96 math.
    /// price = (y + r) / (x + h). Returned as sqrt(price) * 2^96.
    function currentPriceX96() external view returns (uint160) {
        uint256 num = ethReserve + virtualEthReserve;
        uint256 den = tokenReserve + virtualTokenReserve;
        if (den == 0) return 0;
        uint256 ratioX96 = (num << 96) / den;
        uint256 s = _sqrt(ratioX96);
        uint256 result = s << 48;
        if (result > type(uint160).max) return type(uint160).max;
        return uint160(result);
    }

    /// @notice Implied market cap in wei = price × totalSupply.
    function marketCapWei() external view returns (uint256) {
        uint256 num = ethReserve + virtualEthReserve;
        uint256 den = tokenReserve + virtualTokenReserve;
        if (den == 0) return 0;
        return (num * token.totalSupply()) / den;
    }


    // --- Trades ------------------------------------------------------------

    function buy(uint256 minTokensOut) external payable nonReentrant returns (uint256 tokensOut) {
        require(!graduated, "graduated");
        require(msg.value > 0, "ethIn=0");
        uint256 ethIntoCurve;
        uint256 platformFee;
        uint256 creatorFee;
        uint256 vaultFee;
        (tokensOut, ethIntoCurve, platformFee, creatorFee, vaultFee) = quoteBuy(msg.value);
        require(tokensOut >= minTokensOut, "slippage");
        require(tokensOut > 0, "out=0");

        // Effects
        tokenReserve -= tokensOut;
        ethReserve += ethIntoCurve;

        // Fee payouts (ETH)
        if (platformFee > 0) _safePayETH(platformFeeReceiver, platformFee);
        if (creatorFee > 0) _safePayETH(creatorFeeReceiver, creatorFee);
        if (vaultFee > 0 && stocksVault != address(0)) _safePayETH(stocksVault, vaultFee);

        // Send tokens to buyer
        require(token.transfer(msg.sender, tokensOut), "xfer");

        emit Buy(msg.sender, msg.value, tokensOut, ethReserve, tokenReserve, block.timestamp);

        // Try to graduate; if it reverts (e.g. pool.initialize race, gas spike),
        // the buy still succeeds and anyone can call graduate() to retry.
        if (ethReserve >= graduationThreshold) {
            try this._graduateExternal() {} catch {}
        }
    }

    /// @notice Public retry — anyone can push the curve into graduation once the
    /// ETH threshold has been reached. Prevents funds from being stranded in the
    /// curve if the atomic graduation inside `buy()` reverted for any reason.
    function graduate() external nonReentrant {
        require(!graduated, "graduated");
        require(ethReserve >= graduationThreshold, "threshold");
        _graduate();
    }

    /// @dev External wrapper so `buy()` can call `_graduate()` in a subcontext
    /// that can be try/catch'd. Only self-callable.
    function _graduateExternal() external {
        require(msg.sender == address(this), "self");
        _graduate();
    }

    function sell(uint256 tokensIn, uint256 minEthOut) external nonReentrant returns (uint256 ethOut) {
        require(!graduated, "graduated");
        require(tokensIn > 0, "in=0");
        uint256 grossEthOut;
        uint256 platformFee;
        uint256 creatorFee;
        uint256 vaultFee;
        (ethOut, grossEthOut, platformFee, creatorFee, vaultFee) = quoteSell(tokensIn);
        require(ethOut >= minEthOut, "slippage");
        require(ethOut > 0, "out=0");

        // Pull tokens
        require(token.transferFrom(msg.sender, address(this), tokensIn), "xferFrom");

        // Effects
        tokenReserve += tokensIn;
        ethReserve -= grossEthOut;

        // Payouts
        if (platformFee > 0) _safePayETH(platformFeeReceiver, platformFee);
        if (creatorFee > 0) _safePayETH(creatorFeeReceiver, creatorFee);
        if (vaultFee > 0 && stocksVault != address(0)) _safePayETH(stocksVault, vaultFee);
        _safePayETH(msg.sender, ethOut);

        emit Sell(msg.sender, tokensIn, ethOut, ethReserve, tokenReserve, block.timestamp);
    }


    // --- Graduation --------------------------------------------------------

    function _graduate() internal {
        graduated = true;
        uint256 wethAmount = ethReserve;
        uint256 tokenAmount = tokenReserve;
        require(wethAmount > 0 && tokenAmount > 0, "empty");

        // Wrap ETH into WETH
        weth.deposit{value: wethAmount}();

        // Determine ordering
        address t = address(token);
        address w = address(weth);
        bool tokenIsToken0 = t < w;
        (address token0, address token1) = tokenIsToken0 ? (t, w) : (w, t);

        // Compute sqrtPriceX96 from final curve reserves
        // price (token1 per token0) = reserve1 / reserve0
        uint256 reserve0 = tokenIsToken0 ? tokenAmount : wethAmount;
        uint256 reserve1 = tokenIsToken0 ? wethAmount : tokenAmount;
        uint160 sqrtPriceX96 = _sqrtPriceX96(reserve1, reserve0);

        // Create + init pool. If the pool exists and is already initialized
        // (race with an external actor), skip init and reuse the existing sqrtPrice.
        address pool = dexFactory.getPool(token0, token1, POOL_FEE);
        if (pool == address(0)) {
            pool = dexFactory.createPool(token0, token1, POOL_FEE);
        }
        try IUniswapV3Pool(pool).initialize(sqrtPriceX96) {} catch {}
        poolAfterGraduation = pool;

        // Approve position manager
        require(token.approve(address(positionManager), tokenAmount), "approve tok");
        require(IERC20(w).approve(address(positionManager), wethAmount), "approve weth");

        // Full range
        int24 minTick = -887272 / TICK_SPACING * TICK_SPACING;
        int24 maxTick =  887272 / TICK_SPACING * TICK_SPACING;

        (uint256 tokenId,,,) = positionManager.mint(INonfungiblePositionManager.MintParams({
            token0: token0,
            token1: token1,
            fee: POOL_FEE,
            tickLower: minTick,
            tickUpper: maxTick,
            amount0Desired: reserve0,
            amount1Desired: reserve1,
            amount0Min: 0,
            amount1Min: 0,
            recipient: address(this),
            deadline: block.timestamp
        }));
        lpTokenId = tokenId;

        // Lock LP: transfer to a fresh LpLocker for this token.
        LpLocker locker = new LpLocker(
            positionManager, weth, t,
            platformFeeReceiver, creatorFeeReceiver,
            /*platformBps*/ 100, /*deployerBps*/ 100, // fixed 50/50 split of Sushi 1% pool fee
            tokenId
        );
        positionManager.safeTransferFrom(address(this), address(locker), tokenId);
        lpLockerAddress = address(locker);

        // Wire the real (now seeded) pool into the token so honeypot scanners
        // that read `token.pool()` simulate against real liquidity. Wrapped in
        // try/catch so a re-entered or already-set pool cannot brick graduation.
        try token.setPool(pool) {} catch {}

        // Zero out reserves (all funds now in pool)
        tokenReserve = 0;
        ethReserve = 0;

        emit Graduated(t, pool, tokenId, wethAmount, tokenAmount, address(locker), block.timestamp);
    }

    // --- helpers -----------------------------------------------------------

    function _safePayETH(address to, uint256 amount) internal {
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "ETH pay");
    }

    /// @dev sqrtPriceX96 = sqrt(reserve1 / reserve0) * 2^96, computed as sqrt(reserve1 * 2^192 / reserve0).
    function _sqrtPriceX96(uint256 reserve1, uint256 reserve0) internal pure returns (uint160) {
        require(reserve0 > 0, "r0=0");
        // Use mulDiv-safe path: sqrt((reserve1 << 192) / reserve0)
        // reserve1 is at most ~4e18, so << 192 fits since Solidity 0.8 handles 256-bit.
        // But (reserve1 << 192) may overflow if reserve1 > 2^64. So use two-step: sqrt(reserve1/reserve0) * 2^96.
        // For our range (reserve1 up to 100 ether = 1e20, reserve0 up to 1e27 tokens), we need higher precision.
        // Approach: compute ratio = (reserve1 * 2^96) / reserve0 (safe since reserve1 * 2^96 <= 2^96 * 1e20 < 2^160),
        // then sqrt(ratio) * 2^48 == sqrt(ratio * 2^96). Actually sqrt(reserve1/reserve0) * 2^96 = sqrt(reserve1 * 2^192 / reserve0).
        // Safer: use 512-bit intermediate via unchecked * shift; we'll approximate with 2-step.
        uint256 ratioX96 = (reserve1 << 96) / reserve0; // Q96 ratio of prices
        // sqrt(ratio) with Q96 → sqrt(ratioX96 * 2^96) = sqrt(ratioX96) * 2^48
        uint256 sqrtRatioX48 = _sqrt(ratioX96);
        uint256 result = sqrtRatioX48 << 48;
        require(result <= type(uint160).max, "sqrtPrice ovf");
        return uint160(result);
    }

    function _sqrt(uint256 x) internal pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = (x + 1) / 2;
        uint256 y = x;
        while (z < y) { y = z; z = (x / z + z) / 2; }
        return y;
    }

    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }

    receive() external payable {}
}
Chain explorer3546msChain node86ms