HamsterLaunchpad

0xe4331d2b57ab6a4451a0f6f8e1a315824a2902dd

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
6,704 bytes
ABI entries
16
11 read · 2 write
License
none

Contract information

Address
0xe4331d2b57ab6a4451a0f6f8e1a315824a2902dd
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x569782e7e4…ea543721f2

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (11)

FEE()uint24
MAX_TICK()int24
SUPPLY()uint256
TICK_SPACING()int24
WETH()address
allTokens(uint256)address
launches(address)address, address, uint256, address
platform()address
platformBps()uint16
positionManager()address
totalLaunches()uint256

Events (2)

FeesClaimedLaunched

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_pm",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_platform",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "_platformBps",
        "type": "uint16"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toCreator0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toCreator1",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toPlatform0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toPlatform1",
        "type": "uint256"
      }
    ],
    "name": "FeesClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      }
    ],
    "name": "Launched",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "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": "claimFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "launches",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platform",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platformBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalLaunches",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {HamsterToken} from "./HamsterToken.sol";

/// Minimal SushiSwap/Uniswap V3 interfaces (function selectors only — ABIs, not
/// implementations). We call the canonical V3 stack directly; no launchpad
/// middleman, no third-party factory.
interface INonfungiblePositionManager {
    function createAndInitializePoolIfNecessary(address token0, address token1, uint24 fee, uint160 sqrtPriceX96)
        external
        payable
        returns (address pool);

    struct MintParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        address recipient;
        uint256 deadline;
    }

    function mint(MintParams calldata p)
        external
        payable
        returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    function collect(CollectParams calldata p) external payable returns (uint256 amount0, uint256 amount1);
}

interface IV3Pool {
    function slot0()
        external
        view
        returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint8, bool);
}

interface IERC20Min {
    function approve(address s, uint256 v) external returns (bool);
    function transfer(address to, uint256 v) external returns (bool);
    function balanceOf(address a) external view returns (uint256);
}

/// @title HamsterLaunchpad
/// @notice HamsterBunker's own launchpad. It deploys an immutable HamsterToken,
///         opens a SushiSwap V3 pool for it, seeds the ENTIRE supply as
///         single-sided liquidity, and locks the LP position here forever (no
///         withdraw path — the LP can never be pulled). Trading fees accrue to
///         the locked position and are split creator / platform on claim.
///
///         Fully independent: tokens launch through THIS contract only. They
///         never touch any third-party launchpad. Source is open + verified.
///
///         The single-sided tick range is computed on-chain from the pool's
///         current tick, so the caller only needs to pass the opening price
///         (sqrtPriceX96). Nothing here can be steered to grief other launches.
contract HamsterLaunchpad {
    address public immutable WETH;
    INonfungiblePositionManager public immutable positionManager;
    address public immutable platform; // HamsterBunker fee recipient
    uint16 public immutable platformBps; // platform cut of trading fees (e.g. 1000 = 10%)

    uint24 public constant FEE = 10000; // 1% pool
    int24 public constant TICK_SPACING = 200; // 1% fee tier spacing
    int24 public constant MAX_TICK = 887200; // 887272 max, aligned down to spacing
    uint256 public constant SUPPLY = 1_000_000_000 ether; // fixed 1B per launch

    struct Launch {
        address token;
        address creator;
        uint256 positionId;
        address pool;
    }

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

    event Launched(address indexed token, address indexed creator, address pool, uint256 positionId, string name, string symbol);
    event FeesClaimed(address indexed token, uint256 toCreator0, uint256 toCreator1, uint256 toPlatform0, uint256 toPlatform1);

    constructor(address _weth, address _pm, address _platform, uint16 _platformBps) {
        require(_platformBps <= 2000, "platformBps too high"); // never more than 20%
        WETH = _weth;
        positionManager = INonfungiblePositionManager(_pm);
        platform = _platform;
        platformBps = _platformBps;
    }

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

    /// @param salt CREATE2 salt (backend picks it so the token address, and thus
    ///        its ordering vs WETH, is known before computing sqrtPriceX96).
    /// @param sqrtPriceX96 opening price (backend computes it for the salt).
    function launch(bytes32 salt, string calldata name, string calldata symbol, address creator, uint160 sqrtPriceX96)
        external
        returns (address token, uint256 positionId)
    {
        require(creator != address(0), "creator=0");

        // 1. deploy the immutable token, full supply to this launchpad
        HamsterToken t = new HamsterToken{salt: salt}(name, symbol, SUPPLY, address(this));
        token = address(t);
        require(launches[token].token == address(0), "exists");

        // 2. order tokens and open the pool at the opening price
        bool tokenIs0 = token < WETH;
        (address token0, address token1) = tokenIs0 ? (token, WETH) : (WETH, token);
        address pool = positionManager.createAndInitializePoolIfNecessary(token0, token1, FEE, sqrtPriceX96);

        // 3. read the current tick and build a SINGLE-SIDED range holding only
        //    the token, so the whole supply sits above (token0) / below (token1)
        //    spot and gets sold into WETH as the price climbs.
        (, int24 cur,,,,,) = IV3Pool(pool).slot0();
        int24 tickLower;
        int24 tickUpper;
        if (tokenIs0) {
            tickLower = (_floorDiv(cur, TICK_SPACING) + 1) * TICK_SPACING; // strictly above spot
            tickUpper = MAX_TICK;
        } else {
            tickUpper = _floorDiv(cur, TICK_SPACING) * TICK_SPACING;
            if (tickUpper >= cur) tickUpper -= TICK_SPACING; // strictly below spot
            tickLower = -MAX_TICK;
        }

        // 4. seed ALL supply as single-sided liquidity and lock the position here
        IERC20Min(token).approve(address(positionManager), SUPPLY);
        (positionId,,,) = positionManager.mint(
            INonfungiblePositionManager.MintParams({
                token0: token0,
                token1: token1,
                fee: FEE,
                tickLower: tickLower,
                tickUpper: tickUpper,
                amount0Desired: tokenIs0 ? SUPPLY : 0,
                amount1Desired: tokenIs0 ? 0 : SUPPLY,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this), // the LP NFT stays here forever = locked
                deadline: block.timestamp
            })
        );

        launches[token] = Launch({token: token, creator: creator, positionId: positionId, pool: pool});
        allTokens.push(token);
        emit Launched(token, creator, pool, positionId, name, symbol);
    }

    /// Harvest the locked position's trading fees and split them creator/platform.
    /// Permissionless — funds only ever go to the creator and the platform.
    function claimFees(address token) external {
        Launch memory L = launches[token];
        require(L.token != address(0), "unknown token");

        (uint256 amt0, uint256 amt1) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: L.positionId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        (address token0, address token1) = token < WETH ? (token, WETH) : (WETH, token);
        uint256 p0 = (amt0 * platformBps) / 10000;
        uint256 p1 = (amt1 * platformBps) / 10000;

        _pay(token0, platform, p0);
        _pay(token1, platform, p1);
        _pay(token0, L.creator, amt0 - p0);
        _pay(token1, L.creator, amt1 - p1);

        emit FeesClaimed(token, amt0 - p0, amt1 - p1, p0, p1);
    }

    function _pay(address erc20, address to, uint256 v) internal {
        if (v > 0) IERC20Min(erc20).transfer(to, v);
    }

    /// floor division for ticks (Solidity `/` truncates toward zero).
    function _floorDiv(int24 a, int24 b) internal pure returns (int24 q) {
        q = a / b;
        if ((a % b != 0) && ((a < 0) != (b < 0))) q -= 1;
    }
}
Chain explorer5032msChain node77ms