HoodxLaunchpad
0x3bb5e5e9fa40c2201f76c7ba8fd987aca1196a48
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
11,888 bytes
ABI entries
23
12 read · 3 write
License
none
Contract information
- Address
- 0x3bb5e5e9fa40c2201f76c7ba8fd987aca1196a48
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x31098c0E25…659D3783Fe
- Creation tx
- 0xbf24c1d074…f63c71edca
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (12)
COIN_SUPPLY() → uint256
LP_FEE() → uint24
TICK_SPACING() → int24
allCoins() → address[]
coinCount() → uint256
coins(uint256) → address
feeHook() → address
isLaunchpadCoin(address) → bool
poolKeyFor(address) → tuple
poolManager() → address
startFdvEth() → uint256
treasury() → address
Events (2)
CoinCreatedLpFeesCollected
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "_poolManager",
"type": "address"
},
{
"internalType": "contract IHooks",
"name": "_feeHook",
"type": "address"
},
{
"internalType": "address",
"name": "_treasury",
"type": "address"
},
{
"internalType": "uint256",
"name": "_startFdvEth",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "NotPoolManager",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "UnknownCoin",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "coin",
"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": "devBuyEth",
"type": "uint256"
}
],
"name": "CoinCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "coin",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "coinAmount",
"type": "uint256"
}
],
"name": "LpFeesCollected",
"type": "event"
},
{
"inputs": [],
"name": "COIN_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "LP_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": "allCoins",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "coinCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "coins",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "coin",
"type": "address"
}
],
"name": "collectLpFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "metadataURI",
"type": "string"
}
],
"name": "createCoin",
"outputs": [
{
"internalType": "address",
"name": "coin",
"type": "address"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "feeHook",
"outputs": [
{
"internalType": "contract IHooks",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isLaunchpadCoin",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "coin",
"type": "address"
}
],
"name": "poolKeyFor",
"outputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startFdvEth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {ModifyLiquidityParams, SwapParams} from "v4-core/src/types/PoolOperation.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {LaunchCoin} from "./LaunchCoin.sol";
/// @title HoodxLaunchpad — bonding curve av laast Uniswap v4-likviditet
/// @notice Én transaksjon: deploy memecoin -> opprett pool med LaunchFeeHook
/// (4 %% -> creator rewards + HDX-treasury) -> HELE supplyen legges
/// som ENSIDIG likviditet fra en gulvpris og oppover (AUDIT-fix M-4).
///
/// KURVEN: startprisen tilsvarer START_FDV (f.eks. 2 ETH for hele
/// supplyen). Kjoepernes ETH akkumuleres i den laaste posisjonen
/// etter hvert som prisen klatrer — én ensidig full-range-posisjon
/// oppfoerer seg som en bonding curve med virtuell dybde = START_FDV.
/// Ingen kan snipe billig supply: foerste X ETH kjoeper ~X/(FDV+X)
/// av supplyen, aldri mer.
///
/// LAAST BY CONSTRUCTION: posisjonen eies av denne kontrakten, som
/// ikke har noen funksjon for aa fjerne likviditet. LP-gebyrer kan
/// hoestes av hvem som helst — alltid til HDX-treasury.
///
/// DEV BUY: creator kan sende ETH med createCoin og faa foerste
/// kjoep i samme transaksjon (betaler samme 4 %% fee som alle andre).
///
/// @dev Prisretning i v4: pris = token1/token0 (coin per ETH). At coinen
/// STIGER i verdi = faerre coin per ETH = tick NED. Buys er zeroForOne.
/// Kurve-omraadet er derfor [minUsableTick, startTick] og poolen
/// initialiseres PAA startTick (posisjonen er da 100 %% coin).
contract HoodxLaunchpad is IUnlockCallback, ReentrancyGuard {
using SafeERC20 for IERC20;
IPoolManager public immutable poolManager;
IHooks public immutable feeHook;
address public immutable treasury;
uint256 public constant COIN_SUPPLY = 1_000_000_000e18; // 1 mrd per coin
uint24 public constant LP_FEE = 3000; // 0,3 %% LP-fee (til treasury)
int24 public constant TICK_SPACING = 60;
/// @notice Virtuell startdybde: FDV ved gulvprisen (eier-valgt, f.eks. 2 ETH).
uint256 public immutable startFdvEth;
// ---- Register ----
address[] public coins;
mapping(address => PoolKey) private poolKeyOf;
mapping(address => bool) public isLaunchpadCoin;
event CoinCreated(
address indexed coin,
address indexed creator,
string name,
string symbol,
string metadataURI,
uint256 devBuyEth
);
event LpFeesCollected(address indexed coin, uint256 ethAmount, uint256 coinAmount);
error UnknownCoin();
error NotPoolManager();
uint8 private constant ACTION_ADD_LIQUIDITY = 1;
uint8 private constant ACTION_COLLECT_FEES = 2;
constructor(IPoolManager _poolManager, IHooks _feeHook, address _treasury, uint256 _startFdvEth) {
require(_startFdvEth >= 0.1 ether, "start FDV too low"); // snipe-vern
poolManager = _poolManager;
feeHook = _feeHook;
treasury = _treasury;
startFdvEth = _startFdvEth;
}
receive() external payable {} // ETH fra poolManager.take ved fee-hoesting
// ------------------------------------------------------------------
// Launch
// ------------------------------------------------------------------
/// @notice Lag en memecoin. msg.value (valgfritt) blir creators foerste
/// kjoep gjennom kurven — samme vilkaar som alle andre.
function createCoin(
string calldata name,
string calldata symbol,
string calldata metadataURI
) external payable nonReentrant returns (address coin) {
coin = address(new LaunchCoin(name, symbol, metadataURI, msg.sender, COIN_SUPPLY));
PoolKey memory key = PoolKey({
currency0: Currency.wrap(address(0)),
currency1: Currency.wrap(coin),
fee: LP_FEE,
tickSpacing: TICK_SPACING,
hooks: feeHook
});
poolKeyOf[coin] = key;
isLaunchpadCoin[coin] = true;
coins.push(coin);
// Gulvprisen: som om hele supplyen var backet av startFdvEth.
(, int24 startTick) = _bondRange();
poolManager.initialize(key, TickMath.getSqrtPriceAtTick(startTick));
poolManager.unlock(abi.encode(ACTION_ADD_LIQUIDITY, coin, msg.value, msg.sender));
emit CoinCreated(coin, msg.sender, name, symbol, metadataURI, msg.value);
}
/// @notice Hoest LP-gebyrer fra en launchpad-pool til HDX-treasury.
function collectLpFees(address coin) external nonReentrant {
if (!isLaunchpadCoin[coin]) revert UnknownCoin();
poolManager.unlock(abi.encode(ACTION_COLLECT_FEES, coin, uint256(0), address(0)));
}
// ------------------------------------------------------------------
// PoolManager-callback
// ------------------------------------------------------------------
function unlockCallback(bytes calldata data) external returns (bytes memory) {
if (msg.sender != address(poolManager)) revert NotPoolManager();
(uint8 action, address coin, uint256 devBuyEth, address creator) =
abi.decode(data, (uint8, address, uint256, address));
PoolKey memory key = poolKeyOf[coin];
(int24 tickLower, int24 tickUpper) = _bondRange();
if (action == ACTION_ADD_LIQUIDITY) {
// Ensidig coin-likviditet [minTick, startTick]; pris staar PAA
// startTick, saa posisjonen krever kun token1 (coin).
uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
uint256 liquidity = FullMath.mulDiv(COIN_SUPPLY, 1 << 96, sqrtUpper - sqrtLower);
// Kjernen runder innskudd OPP — margin saa vi aldri skylder mer
// coin enn vi holder.
if (liquidity > 1000) liquidity -= 1000;
(BalanceDelta mod,) = poolManager.modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: int256(liquidity),
salt: bytes32(0)
}),
""
);
// Betal coin-siden (ETH-siden er 0 ved start paa oevre tick).
if (mod.amount1() < 0) {
poolManager.sync(key.currency1);
IERC20(coin).safeTransfer(
address(poolManager), uint256(uint128(-mod.amount1()))
);
poolManager.settle();
}
// Valgfritt dev buy: creators foerste kjoep gjennom kurven.
if (devBuyEth > 0) {
BalanceDelta s = poolManager.swap(
key,
SwapParams({
zeroForOne: true,
amountSpecified: -int256(devBuyEth),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
}),
""
);
if (s.amount0() < 0) {
poolManager.settle{value: uint256(uint128(-s.amount0()))}();
}
if (s.amount1() > 0) {
poolManager.take(key.currency1, creator, uint256(uint128(s.amount1())));
}
}
} else if (action == ACTION_COLLECT_FEES) {
(BalanceDelta delta,) = poolManager.modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: 0,
salt: bytes32(0)
}),
""
);
uint256 ethOut;
uint256 coinOut;
if (delta.amount0() > 0) {
ethOut = uint256(uint128(delta.amount0()));
poolManager.take(key.currency0, treasury, ethOut);
}
if (delta.amount1() > 0) {
coinOut = uint256(uint128(delta.amount1()));
poolManager.take(key.currency1, treasury, coinOut);
}
emit LpFeesCollected(coin, ethOut, coinOut);
}
return "";
}
// ------------------------------------------------------------------
// Views
// ------------------------------------------------------------------
function coinCount() external view returns (uint256) {
return coins.length;
}
function allCoins() external view returns (address[] memory) {
return coins;
}
function poolKeyFor(address coin) external view returns (PoolKey memory) {
if (!isLaunchpadCoin[coin]) revert UnknownCoin();
return poolKeyOf[coin];
}
/// @notice Kurve-omraadet (likt for alle coins: samme supply og start-FDV).
function _bondRange() internal view returns (int24 tickLower, int24 tickUpper) {
tickLower = TickMath.minUsableTick(TICK_SPACING);
// P_start = COIN_SUPPLY / startFdvEth (coin per ETH ved gulvet).
uint256 ratioX192 = FullMath.mulDiv(COIN_SUPPLY, 1 << 192, startFdvEth);
// forge-lint: disable-next-line(unsafe-typecast)
uint160 sqrtStart = uint160(_sqrt(ratioX192)); // trygt: < MAX_SQRT_PRICE for alle rimelige FDV
int24 raw = TickMath.getTickAtSqrtPrice(sqrtStart);
tickUpper = _floorToSpacing(raw, TICK_SPACING);
}
function _floorToSpacing(int24 tick, int24 spacing) internal pure returns (int24) {
int24 q = tick / spacing;
if (tick < 0 && tick % spacing != 0) q--;
return q * spacing;
}
function _sqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
Chain explorer3236msChain node96ms