LaunchpadLoot

0x984a1306c5a9bfaafa0bbbfda451a473ad3dcfd9

Verification
Verified
v0.8.25+commit.b61c2a91
Type
Contract
15,502 bytes
ABI entries
29
19 read · 7 write
License
none

Contract information

Address
0x984a1306c5a9bfaafa0bbbfda451a473ad3dcfd9
Chain
Robinhood Chain (4663)
Compiler
v0.8.25+commit.b61c2a91
Optimization
Enabled
Creation tx
0xe7f0d8cb09…cee298e74c

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (19)

SUPPLY()uint256
allLaunches()tuple[]
createFeeWei()uint256
domainSeparator()bytes32
fee()uint24
launchAt(uint256)tuple
launchCount()uint256
launches(uint256)address, address, address, string, string, string, uint64, uint256
locker()address
nonces(address)uint256
owner()address
platform()address
poolOf(address)address
positionManager()address
sqrtPriceX96()uint160
swapRouter()address
tickLower()int24
tickUpper()int24
weth()address

Events (1)

Launched

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "swapRouter_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "locker_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "platform_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "devBuyTokens",
        "type": "uint256"
      }
    ],
    "name": "Launched",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "allLaunches",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pool",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "uint64",
            "name": "createdAt",
            "type": "uint64"
          },
          {
            "internalType": "uint256",
            "name": "lpTokenId",
            "type": "uint256"
          }
        ],
        "internalType": "struct LaunchpadLoot.Launch[]",
        "name": "",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "createFeeWei",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "createFor",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "domainSeparator",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "fee",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "i",
        "type": "uint256"
      }
    ],
    "name": "launchAt",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pool",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "uint64",
            "name": "createdAt",
            "type": "uint64"
          },
          {
            "internalType": "uint256",
            "name": "lpTokenId",
            "type": "uint256"
          }
        ],
        "internalType": "struct LaunchpadLoot.Launch",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "launches",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "uint64",
        "name": "createdAt",
        "type": "uint64"
      },
      {
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "locker",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "nonces",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platform",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "poolOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "rescueETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "wei_",
        "type": "uint256"
      }
    ],
    "name": "setCreateFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "p",
        "type": "address"
      }
    ],
    "name": "setPlatform",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96_",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "tickLower_",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper_",
        "type": "int24"
      },
      {
        "internalType": "uint24",
        "name": "fee_",
        "type": "uint24"
      }
    ],
    "name": "setSeedParams",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sqrtPriceX96",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tickLower",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tickUpper",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "n",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
//
// =========================================================
//       ____        __    _          __                __
//      / __ \____  / /_  (_)___     / /   ____  ____  / /_
//     / /_/ / __ \/ __ \/ / __ \   / /   / __ \/ __ \/ __/
//    / _, _/ /_/ / /_/ / / / / /  / /___/ /_/ / /_/ / /_
//   /_/ |_|\____/_.___/_/_/ /_/  /_____/\____/\____/\__/
// =========================================================
//
pragma solidity ^0.8.25;

import { LaunchToken } from "./LaunchToken.sol";
import {
    INonfungiblePositionManager,
    SwapCompat,
    IWETH9,
    IERC20Min,
    ILootLocker
} from "./RobinLootInterfaces.sol";

/**
 * @title LaunchpadLoot
 * @notice One-tx mint + launch of a fixed-supply, admin-less LaunchToken straight
 *         into a locked single-sided Uniswap V3 position — no bonding curve, no
 *         graduation. The token is a real, tradeable pool from block one, so
 *         Uniswap routers and DexScreener charts work natively.
 *
 *         All 1,000,000 supply is provided as token-only liquidity in a wide range
 *         whose upper bound sits just below the start price, so the position is
 *         100% token (zero WETH) and buys walk the price up the range.
 *
 *         Token/WETH ordering: we deploy each LaunchToken via CREATE2 and search for
 *         a salt whose resulting address sorts ABOVE WETH, so token0 = WETH and
 *         token1 = the LaunchToken for every launch. That lets one pinned
 *         sqrtPriceX96 + tick range give an identical starting market cap to every
 *         token (ETH-pinned).
 *
 *         launch() flow (one tx):
 *           1. CREATE2-deploy a LaunchToken (full 1M minted to this factory,
 *              no owner/mint/pause — born immutable).
 *           2. create+init the 1% V3 pool at the pinned price.
 *           3. mint the single-sided position to the LootLocker (locked forever).
 *           4. register the lock with the creator as fee beneficiary.
 *           5. optional dev-buy: swap the creator's ETH → token on the same pool.
 *
 *         No per-trade fee — trading happens natively on Uniswap.
 */
contract LaunchpadLoot {
    address public owner;
    address public immutable positionManager;
    address public immutable swapRouter;
    address public immutable weth;
    address public immutable locker;
    address public platform; // create-fee sink (fee is 0 by default)

    uint256 public constant SUPPLY = 1_000_000e18;

    // ---- pinned single-sided seed params (owner-configurable for FUTURE launches;
    //      existing pools are immutable real Uniswap pools) ----
    uint24 public fee = 10_000;                                          // 1%
    int24 public tickLower = -887_200;                                   // wide floor
    int24 public tickUpper = 131_200;                                    // just below start tick
    uint160 public sqrtPriceX96 = 56022770974786141989122762342400;      // ~$5k MC, ETH-pinned
    uint256 public createFeeWei = 0;                                     // free to launch

    struct Launch {
        address token;
        address pool;
        address creator;
        string name;
        string symbol;
        string metadataURI;
        uint64 createdAt;
        uint256 lpTokenId;
    }
    Launch[] public launches;
    mapping(address => address) public poolOf; // token => pool
    uint256 public launchCount;

    // EIP-712 gasless launches.
    mapping(address => uint256) public nonces;
    bytes32 private constant _EIP712_DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    bytes32 private constant _LAUNCH_TYPEHASH =
        keccak256("Launch(address creator,string name,string symbol,string metadataURI,uint256 nonce,uint256 deadline)");
    bytes32 private constant _NAME_HASH = keccak256(bytes("RobinLootLaunchpad"));
    bytes32 private constant _VERSION_HASH = keccak256(bytes("1"));

    bool private _entered;
    modifier nonReentrant() { require(!_entered, "re"); _entered = true; _; _entered = false; }
    modifier onlyOwner() { require(msg.sender == owner, "o"); _; }

    event Launched(
        address indexed token,
        address indexed pool,
        address indexed creator,
        string name,
        string symbol,
        string metadataURI,
        uint256 lpTokenId,
        uint256 devBuyTokens
    );

    constructor(address positionManager_, address swapRouter_, address weth_, address locker_, address platform_) {
        require(
            positionManager_ != address(0) && swapRouter_ != address(0) &&
            weth_ != address(0) && locker_ != address(0) && platform_ != address(0),
            "zero"
        );
        owner = msg.sender;
        positionManager = positionManager_;
        swapRouter = swapRouter_;
        weth = weth_;
        locker = locker_;
        platform = platform_;
    }

    /// Launch a token. Caller is the creator; msg.value beyond createFeeWei is an
    /// optional dev-buy executed as a normal swap against the fresh pool.
    function launch(string calldata name, string calldata symbol, string calldata metadataURI)
        external
        payable
        nonReentrant
        returns (address token, address pool)
    {
        return _launch(msg.sender, name, symbol, metadataURI, msg.value);
    }

    /// Gasless launch for ANY wallet: `creator` signs an EIP-712 Launch request
    /// off-chain and a relayer submits it here and pays gas. No dev-buy on this
    /// path (it would need the creator's ETH). The token is credited to `creator`.
    function createFor(
        address creator,
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        uint256 nonce,
        uint256 deadline,
        bytes calldata signature
    ) external nonReentrant returns (address token, address pool) {
        require(block.timestamp <= deadline, "expired");
        require(nonce == nonces[creator], "nonce");
        _verifyLaunchSig(creator, name, symbol, metadataURI, nonce, deadline, signature);
        nonces[creator] = nonce + 1;
        return _launch(creator, name, symbol, metadataURI, 0);
    }

    function _launch(
        address creator,
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        uint256 valueIn
    ) internal returns (address token, address pool) {
        require(valueIn >= createFeeWei, "fee");

        token = _deployToken(creator, name, symbol);
        uint256 lpTokenId;
        (pool, lpTokenId) = _seed(token, creator);

        if (createFeeWei > 0) {
            (bool ok, ) = platform.call{value: createFeeWei}("");
            require(ok, "fee-send");
        }

        uint256 devBuyTokens;
        uint256 devWei = valueIn - createFeeWei;
        if (devWei > 0) devBuyTokens = _devBuy(token, creator, devWei);

        _register(token, pool, creator, name, symbol, metadataURI, lpTokenId, devBuyTokens);
    }

    /// Born-renounced, fixed-supply LaunchToken: full 1M minted to THIS factory,
    /// no owner, no mint, no pause. CREATE2-deployed with a salt chosen so the
    /// token address sorts above WETH → token0 = WETH always.
    function _deployToken(address creator, string calldata name, string calldata symbol)
        internal
        returns (address token)
    {
        bytes memory initCode = abi.encodePacked(
            type(LaunchToken).creationCode,
            abi.encode(name, symbol, SUPPLY, address(this))
        );
        bytes32 initHash = keccak256(initCode);
        bytes32 salt = keccak256(abi.encode(creator, launchCount));
        // Bump the salt until the predicted CREATE2 address sorts above WETH.
        while (uint160(_create2Address(salt, initHash)) <= uint160(weth)) {
            salt = keccak256(abi.encodePacked(salt));
        }
        token = address(new LaunchToken{salt: salt}(name, symbol, SUPPLY, address(this)));
        // Invariant: token > WETH → token0 = WETH always.
        require(uint160(token) > uint160(weth), "order");
        require(IERC20Min(token).balanceOf(address(this)) == SUPPLY, "mint");
    }

    /// EIP-1014 CREATE2 address prediction for a given salt + init-code hash.
    function _create2Address(bytes32 salt, bytes32 initHash) internal view returns (address) {
        return address(uint160(uint256(
            keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initHash))
        )));
    }

    /// Seed the single-sided position (all token1, zero WETH) into the locker.
    function _seed(address token, address creator) internal returns (address pool, uint256 lpTokenId) {
        require(IERC20Min(token).approve(positionManager, SUPPLY), "approve");
        pool = INonfungiblePositionManager(positionManager).createAndInitializePoolIfNecessary(
            weth, token, fee, sqrtPriceX96
        );
        (lpTokenId, , , ) = INonfungiblePositionManager(positionManager).mint(
            INonfungiblePositionManager.MintParams({
                token0: weth,
                token1: token,
                fee: fee,
                tickLower: tickLower,
                tickUpper: tickUpper,
                amount0Desired: 0,
                amount1Desired: SUPPLY,
                amount0Min: 0,
                amount1Min: 0,
                recipient: locker,
                deadline: block.timestamp
            })
        );
        ILootLocker(locker).register(lpTokenId, creator);
        poolOf[token] = pool;
    }

    /// Optional launch dev-buy: wrap ETH and swap WETH→token on the new pool,
    /// delivering the bought tokens straight to the creator.
    function _devBuy(address token, address creator, uint256 wei_) internal returns (uint256 out) {
        IWETH9(weth).deposit{value: wei_}();
        require(IWETH9(weth).approve(swapRouter, wei_), "approve-weth");
        // exactInputSingle via the SwapRouter variant this chain runs (with/without deadline).
        out = SwapCompat.exactInputSingle(swapRouter, weth, token, fee, creator, wei_, 0);
    }

    function _register(
        address token,
        address pool,
        address creator,
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        uint256 lpTokenId,
        uint256 devBuyTokens
    ) internal {
        launches.push(Launch({
            token: token,
            pool: pool,
            creator: creator,
            name: name,
            symbol: symbol,
            metadataURI: metadataURI,
            createdAt: uint64(block.timestamp),
            lpTokenId: lpTokenId
        }));
        launchCount++;
        emit Launched(token, pool, creator, name, symbol, metadataURI, lpTokenId, devBuyTokens);
    }

    // ---- EIP-712 (gasless createFor) ----
    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(_EIP712_DOMAIN_TYPEHASH, _NAME_HASH, _VERSION_HASH, block.chainid, address(this)));
    }

    function _verifyLaunchSig(
        address creator,
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        uint256 nonce,
        uint256 deadline,
        bytes calldata sig
    ) internal view {
        bytes32 structHash = keccak256(abi.encode(
            _LAUNCH_TYPEHASH, creator,
            keccak256(bytes(name)), keccak256(bytes(symbol)), keccak256(bytes(metadataURI)),
            nonce, deadline
        ));
        bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), domainSeparator(), structHash));
        require(sig.length == 65, "siglen");
        bytes32 r = bytes32(sig[0:32]);
        bytes32 s = bytes32(sig[32:64]);
        uint8 v = uint8(sig[64]);
        address signer = ecrecover(digest, v, r, s);
        require(signer != address(0) && signer == creator, "sig");
    }

    // ---- views ----
    function allLaunches() external view returns (Launch[] memory) { return launches; }
    function launchAt(uint256 i) external view returns (Launch memory) { return launches[i]; }

    // ---- admin ----
    function setCreateFee(uint256 wei_) external onlyOwner { createFeeWei = wei_; }
    function setSeedParams(uint160 sqrtPriceX96_, int24 tickLower_, int24 tickUpper_, uint24 fee_) external onlyOwner {
        require(tickLower_ < tickUpper_, "ticks");
        sqrtPriceX96 = sqrtPriceX96_;
        tickLower = tickLower_;
        tickUpper = tickUpper_;
        fee = fee_;
    }
    function setPlatform(address p) external onlyOwner { require(p != address(0), "zero"); platform = p; }
    function transferOwnership(address n) external onlyOwner { require(n != address(0), "zero"); owner = n; }

    /// Sweep any ETH stuck here (launch() forwards all msg.value; dust only).
    function rescueETH(address to) external onlyOwner {
        require(to != address(0), "zero");
        (bool ok, ) = to.call{value: address(this).balance}("");
        require(ok, "send");
    }

    receive() external payable {}
}
Chain explorer6364msChain node94ms