Launcher

0xb25e66ef798adef8a41cef15136613971e99b557

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
8,999 bytes
ABI entries
21
9 read · 6 write
License
none

Contract information

Address
0xb25e66ef798adef8a41cef15136613971e99b557
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0xc54a925c35…2e74558c45

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (9)

FEE()uint24
NPM()address
ROUTER()address
WETH()address
allTokens(uint256)address
launches(address)address, address, uint256, uint256
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
tokenCount()uint256

Events (4)

FeesCollectedOwnershipTransferredRescuedTokenCreated

ABI

[
  {
    "inputs": [],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "name": "FeesCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "Rescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "NPM",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROUTER",
    "outputs": [
      {
        "internalType": "contract ISwapRouter02",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "allTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "collectFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "launches",
    "outputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "mainId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tailId",
        "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": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueERC20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "int256",
        "name": "amount0Delta",
        "type": "int256"
      },
      {
        "internalType": "int256",
        "name": "amount1Delta",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "uniswapV3SwapCallback",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

// One-transaction token launcher for Uniswap V3 on Robinhood Chain (4663).
//
// createToken(name, symbol) payable:
//   1. deploys a fixed-supply 1B ERC20 (no owner, no mint — renounced by construction)
//   2. creates + initializes the TOKEN/WETH 1% pool at ~8.4e-9 ETH per token
//      (tick ±186001 — same curve the STIMULUS launch used)
//   3. mints two one-sided LP tranches: MAIN 900M across a 16x price range,
//      TAIL (remaining balance) out to the range edge. Both NFTs stay in this
//      contract forever — liquidity is locked, creators collect the trading fees.
//   4. if ETH was sent, buys the token atomically (dev buy) to msg.sender.
//
// The frontend discovers the token address from the TokenCreated event.

interface IERC20Like {
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address a) external view returns (uint256);
}

interface INonfungiblePositionManager {
    struct MintParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        address recipient;
        uint256 deadline;
    }
    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }
    function createAndInitializePoolIfNecessary(address token0, address token1, uint24 fee, uint160 sqrtPriceX96)
        external payable returns (address pool);
    function mint(MintParams calldata params)
        external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    function collect(CollectParams calldata params)
        external payable returns (uint256 amount0, uint256 amount1);
}

interface ISwapRouter02 {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
}

interface IUniswapV3PoolMinimal {
    function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint8, bool);
    function swap(address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data)
        external returns (int256 amount0, int256 amount1);
}

contract LaunchToken {
    string public name;
    string public symbol;
    uint8 public constant decimals = 18;
    uint256 public constant totalSupply = 1_000_000_000e18;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    constructor(string memory name_, string memory symbol_, address to) {
        name = name_;
        symbol = symbol_;
        balanceOf[to] = totalSupply;
        emit Transfer(address(0), to, totalSupply);
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        return _transfer(msg.sender, to, amount);
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        uint256 allowed = allowance[from][msg.sender];
        if (allowed != type(uint256).max) {
            require(allowed >= amount, "allowance");
            allowance[from][msg.sender] = allowed - amount;
        }
        return _transfer(from, to, amount);
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function _transfer(address from, address to, uint256 amount) internal returns (bool) {
        require(balanceOf[from] >= amount, "balance");
        unchecked {
            balanceOf[from] -= amount;
            balanceOf[to] += amount;
        }
        emit Transfer(from, to, amount);
        return true;
    }
}

contract Launcher {
    INonfungiblePositionManager public constant NPM =
        INonfungiblePositionManager(0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3);
    ISwapRouter02 public constant ROUTER = ISwapRouter02(0xCaf681a66D020601342297493863E78C959E5cb2);
    address public constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;

    uint24 public constant FEE = 10000; // 1%, tick spacing 200

    // Price curve (mirrors the live STIMULUS launch):
    //   start ~1.19e8 tokens per ETH → 1B supply ≈ 8.4 ETH starting FDV.
    //   MAIN 900M sits across ticks [158000, 186000] (a 16.4x price climb),
    //   TAIL covers the rest of the range so the pool never runs dry.
    int24 internal constant TICK_A = 158000;
    int24 internal constant TICK_B = 186000;
    int24 internal constant TICK_EDGE = 887200;
    // sqrtPriceX96 at tick +186001 / -186001 (token above / below WETH by address sort)
    uint160 internal constant SQRT_HI = 866239624055612229356474014367744;
    uint160 internal constant SQRT_LO = 7246380286782740616495968;

    uint256 internal constant MAIN_AMOUNT = 900_000_000e18;

    struct Launch {
        address creator;
        address pool;
        uint256 mainId;
        uint256 tailId;
    }

    mapping(address => Launch) public launches;
    address[] public allTokens;

    address public owner;

    // The frontend matches /create|launch/i on the event name and reads args.token.
    event TokenCreated(address indexed creator, address indexed token, string name, string symbol);
    event FeesCollected(address indexed token, address indexed creator, uint256 amount0, uint256 amount1);
    event OwnershipTransferred(address indexed from, address indexed to);
    event Rescued(address indexed token, address indexed to, uint256 amount);

    address internal expectedPool; // transient guard for the swap callback

    constructor() {
        owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function transferOwnership(address to) external onlyOwner {
        emit OwnershipTransferred(owner, to);
        owner = to;
    }

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

    // --- Rescue (safety net) -------------------------------------------------
    // Recovers ERC20s or ETH that get accidentally sent to this contract.
    // It CANNOT touch launched liquidity: the LP positions are Uniswap v3
    // ERC721 NFTs, and there is no function anywhere in this contract that
    // transfers those NFTs or reduces their liquidity — so every launch's LP
    // stays permanently locked, rescue or not.
    function rescueERC20(address token, address to, uint256 amount) external onlyOwner {
        IERC20Like(token).transfer(to, amount);
        emit Rescued(token, to, amount);
    }

    function rescueETH(address to, uint256 amount) external onlyOwner {
        (bool ok, ) = payable(to).call{value: amount}("");
        require(ok, "eth transfer failed");
        emit Rescued(address(0), to, amount);
    }

    receive() external payable {} // accept stray ETH so it can be rescued

    function createToken(string calldata name, string calldata symbol) external payable returns (address token) {
        // Salt includes block.number so a griefed (pre-created, badly priced) pool
        // never permanently blocks the same predicted address.
        bytes32 salt = keccak256(abi.encode(msg.sender, name, symbol, block.number, allTokens.length));
        token = address(new LaunchToken{salt: salt}(name, symbol, address(this)));

        bool tokenIs1 = token > WETH;
        (address t0, address t1) = tokenIs1 ? (WETH, token) : (token, WETH);
        uint160 sqrtPrice = tokenIs1 ? SQRT_HI : SQRT_LO;

        address pool = NPM.createAndInitializePoolIfNecessary(t0, t1, FEE, sqrtPrice);
        _ensureStartPrice(pool, tokenIs1, sqrtPrice);

        IERC20Like(token).approve(address(NPM), type(uint256).max);

        uint256 mainId = _mintSide(t0, t1, tokenIs1,
            tokenIs1 ? TICK_A : -TICK_B,
            tokenIs1 ? TICK_B : -TICK_A,
            MAIN_AMOUNT);
        uint256 tailId = _mintSide(t0, t1, tokenIs1,
            tokenIs1 ? -TICK_EDGE : -TICK_A,
            tokenIs1 ? TICK_A : TICK_EDGE,
            IERC20Like(token).balanceOf(address(this)));

        launches[token] = Launch(msg.sender, pool, mainId, tailId);
        allTokens.push(token);
        emit TokenCreated(msg.sender, token, name, symbol);

        if (msg.value > 0) {
            ROUTER.exactInputSingle{value: msg.value}(ISwapRouter02.ExactInputSingleParams({
                tokenIn: WETH,
                tokenOut: token,
                fee: FEE,
                recipient: msg.sender,
                amountIn: msg.value,
                amountOutMinimum: 0, // atomic with pool creation — nothing can interpose
                sqrtPriceLimitX96: 0
            }));
        }
    }

    /// LP fees accrue to the locked positions; the token's creator can sweep them.
    function collectFees(address token) external returns (uint256 amount0, uint256 amount1) {
        Launch storage l = launches[token];
        require(msg.sender == l.creator, "not creator");
        (uint256 a0, uint256 a1) = NPM.collect(INonfungiblePositionManager.CollectParams(
            l.mainId, l.creator, type(uint128).max, type(uint128).max));
        (uint256 b0, uint256 b1) = NPM.collect(INonfungiblePositionManager.CollectParams(
            l.tailId, l.creator, type(uint128).max, type(uint128).max));
        amount0 = a0 + b0;
        amount1 = a1 + b1;
        emit FeesCollected(token, l.creator, amount0, amount1);
    }

    function _mintSide(address t0, address t1, bool tokenIs1, int24 lower, int24 upper, uint256 amount)
        internal returns (uint256 tokenId)
    {
        (tokenId,,,) = NPM.mint(INonfungiblePositionManager.MintParams({
            token0: t0,
            token1: t1,
            fee: FEE,
            tickLower: lower,
            tickUpper: upper,
            amount0Desired: tokenIs1 ? 0 : amount,
            amount1Desired: tokenIs1 ? amount : 0,
            amount0Min: 0,
            amount1Min: 0,
            recipient: address(this),
            deadline: block.timestamp
        }));
    }

    // If someone pre-created the pool at a different price (it had to be priced
    // before our token existed, so it holds no liquidity we care about), walk the
    // price back to our start via a swap through the empty book. Crossing zero
    // liquidity owes ~nothing; any dust owed is paid from our 1B token balance,
    // which the balance-based TAIL mint then absorbs.
    function _ensureStartPrice(address pool, bool tokenIs1, uint160 target) internal {
        (uint160 current,,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
        if (current == target) return;
        expectedPool = pool;
        // moving toward target: zeroForOne=true pushes sqrtPrice down
        bool zeroForOne = current > target;
        // paying side must be our token, which we hold — never WETH
        require(zeroForOne != tokenIs1, "pool griefed: bad direction");
        IUniswapV3PoolMinimal(pool).swap(address(this), zeroForOne, int256(1), target, "");
        expectedPool = address(0);
        (current,,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
        require(current == target, "pool griefed: price stuck");
        // token side of a fresh launch must be fully one-sided
        (, int24 tick,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
        require(tokenIs1 ? tick >= TICK_B : tick < -TICK_B, "pool griefed: tick");
    }

    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {
        require(msg.sender == expectedPool, "bad callback");
        if (amount0Delta > 0) {
            IERC20Like(IUniswapV3PoolLike(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        }
        if (amount1Delta > 0) {
            IERC20Like(IUniswapV3PoolLike(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }

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

interface IUniswapV3PoolLike {
    function token0() external view returns (address);
    function token1() external view returns (address);
}
Chain explorer2229msChain node78ms