HdxCurveSeeder

0xe61b2a3ea93a33ae8449b7539a27f4e631a19f42

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
7,097 bytes
ABI entries
25
11 read · 3 write
License
none

Contract information

Address
0xe61b2a3ea93a33ae8449b7539a27f4e631a19f42
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xb3e1d44d1f…b475646f32

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (11)

LP_FEE()uint24
TICK_SPACING()int24
deployer()address
feeHook()address
hdx()address
poolKey()tuple
poolManager()address
seeded()bool
seededSupply()uint256
startFdvEth()uint256
treasury()address

Events (2)

LpFeesCollectedSeeded

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "_poolManager",
        "type": "address"
      },
      {
        "internalType": "contract IHooks",
        "name": "_feeHook",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_treasury",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "_hdx",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "_startFdvEth",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadySeeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPoolManager",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotSeeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NothingToSeed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyDeployer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "hdxAmount",
        "type": "uint256"
      }
    ],
    "name": "LpFeesCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "supply",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "startFdvEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      }
    ],
    "name": "Seeded",
    "type": "event"
  },
  {
    "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": "collectLpFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deployer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeHook",
    "outputs": [
      {
        "internalType": "contract IHooks",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hdx",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolKey",
    "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": "seed",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "seeded",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "seededSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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} 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";

/// @title HdxCurveSeeder — HDX sin egen bonding curve-launch (NULL ETH-seed)
/// @notice Eier-beslutning 16/7-2026: 100 % av HDX-supplyen launches paa en
///         bonding curve, samme moenster som launchpaden — ENSIDIG laast
///         likviditet fra gulv-FDV (2 ETH) og oppover, med HoodxFeeHook
///         (4 % -> treasury) paa poolen. Ingen ETH-seed, ingen team-allokering.
///
///         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 treasury. (Erstatter
///         HoodxLpLock for HDX-poolen: det finnes ingen posisjons-NFT aa
///         laase — posisjonen kan aldri roeres.)
///
/// @dev Flyt i deploy-scriptet (én broadcast):
///        1. deploy seeder  2. token.setExcludedFromRewards(seeder)
///        3. token.transfer(seeder, HELE supplyen)  4. seeder.seed()
///      seed() kan kun kalles av deployeren, kun én gang.
///
/// @dev Prisretning som i launchpaden: pris = token1/token0 (HDX per ETH).
///      HDX stiger i verdi = tick NED. Buys er zeroForOne. Kurve-omraadet er
///      [minUsableTick, startTick] og poolen initialiseres PAA startTick
///      (posisjonen er da 100 % HDX). Kurven oppfoerer seg som konstant-
///      produkt med virtuell ETH-dybde = startFdvEth (verifisert i I-10):
///      foerste X ETH kjoeper ~X/(startFdv+X) av supplyen — aldri mer.
contract HdxCurveSeeder is IUnlockCallback, ReentrancyGuard {
    using SafeERC20 for IERC20;

    IPoolManager public immutable poolManager;
    IHooks public immutable feeHook;
    address public immutable treasury;
    IERC20 public immutable hdx;
    address public immutable deployer;

    /// @notice Virtuell startdybde: FDV ved gulvprisen (2 ETH i deploy-scriptet).
    uint256 public immutable startFdvEth;

    uint24 public constant LP_FEE = 3000; // 0,3 % LP-fee (hoestes til treasury)
    int24 public constant TICK_SPACING = 60;

    PoolKey private _poolKey;
    bool public seeded;
    uint256 public seededSupply;

    event Seeded(uint256 supply, uint256 startFdvEth, int24 tickLower, int24 tickUpper);
    event LpFeesCollected(uint256 ethAmount, uint256 hdxAmount);

    error OnlyDeployer();
    error AlreadySeeded();
    error NotSeeded();
    error NothingToSeed();
    error NotPoolManager();

    uint8 private constant ACTION_ADD_LIQUIDITY = 1;
    uint8 private constant ACTION_COLLECT_FEES = 2;

    constructor(
        IPoolManager _poolManager,
        IHooks _feeHook,
        address _treasury,
        IERC20 _hdx,
        uint256 _startFdvEth
    ) {
        require(_startFdvEth >= 0.1 ether, "start FDV too low"); // snipe-vern
        poolManager = _poolManager;
        feeHook = _feeHook;
        treasury = _treasury;
        hdx = _hdx;
        startFdvEth = _startFdvEth;
        deployer = msg.sender;
    }

    receive() external payable {} // ETH fra poolManager.take ved fee-hoesting

    // ------------------------------------------------------------------
    //  Launch: hele HDX-beholdningen inn som laast ensidig likviditet
    // ------------------------------------------------------------------

    function seed() external nonReentrant {
        if (msg.sender != deployer) revert OnlyDeployer();
        if (seeded) revert AlreadySeeded();
        uint256 supply = hdx.balanceOf(address(this));
        if (supply == 0) revert NothingToSeed();
        seeded = true;
        seededSupply = supply;

        _poolKey = PoolKey({
            currency0: Currency.wrap(address(0)), // native ETH (kreves av hooken)
            currency1: Currency.wrap(address(hdx)),
            fee: LP_FEE,
            tickSpacing: TICK_SPACING,
            hooks: feeHook
        });

        (int24 tickLower, int24 tickUpper) = _bondRange();
        poolManager.initialize(_poolKey, TickMath.getSqrtPriceAtTick(tickUpper));
        poolManager.unlock(abi.encode(ACTION_ADD_LIQUIDITY));

        emit Seeded(supply, startFdvEth, tickLower, tickUpper);
    }

    /// @notice Hoest LP-gebyrer fra HDX-poolen til treasury. Aapen for alle.
    function collectLpFees() external nonReentrant {
        if (!seeded) revert NotSeeded();
        poolManager.unlock(abi.encode(ACTION_COLLECT_FEES));
    }

    // ------------------------------------------------------------------
    //  PoolManager-callback
    // ------------------------------------------------------------------

    function unlockCallback(bytes calldata data) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert NotPoolManager();
        uint8 action = abi.decode(data, (uint8));
        (int24 tickLower, int24 tickUpper) = _bondRange();

        if (action == ACTION_ADD_LIQUIDITY) {
            // Ensidig HDX-likviditet [minTick, startTick]; prisen staar PAA
            // startTick, saa posisjonen krever kun token1 (HDX).
            uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
            uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
            uint256 liquidity = FullMath.mulDiv(seededSupply, 1 << 96, sqrtUpper - sqrtLower);
            // Kjernen runder innskudd OPP — margin saa vi aldri skylder mer
            // HDX enn vi holder (AUDIT L-2).
            if (liquidity > 1000) liquidity -= 1000;

            (BalanceDelta mod,) = poolManager.modifyLiquidity(
                _poolKey,
                ModifyLiquidityParams({
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    liquidityDelta: int256(liquidity),
                    salt: bytes32(0)
                }),
                ""
            );
            // Betal HDX-siden (ETH-siden er 0 ved start paa oevre tick).
            if (mod.amount1() < 0) {
                poolManager.sync(_poolKey.currency1);
                hdx.safeTransfer(address(poolManager), uint256(uint128(-mod.amount1())));
                poolManager.settle();
            }
        } else if (action == ACTION_COLLECT_FEES) {
            (BalanceDelta delta,) = poolManager.modifyLiquidity(
                _poolKey,
                ModifyLiquidityParams({
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    liquidityDelta: 0,
                    salt: bytes32(0)
                }),
                ""
            );
            uint256 ethOut;
            uint256 hdxOut;
            if (delta.amount0() > 0) {
                ethOut = uint256(uint128(delta.amount0()));
                poolManager.take(_poolKey.currency0, treasury, ethOut);
            }
            if (delta.amount1() > 0) {
                hdxOut = uint256(uint128(delta.amount1()));
                poolManager.take(_poolKey.currency1, treasury, hdxOut);
            }
            emit LpFeesCollected(ethOut, hdxOut);
        }
        return "";
    }

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

    function poolKey() external view returns (PoolKey memory) {
        if (!seeded) revert NotSeeded();
        return _poolKey;
    }

    /// @notice Kurve-omraadet: gulvpris som om hele supplyen var backet av
    ///         startFdvEth.
    function _bondRange() internal view returns (int24 tickLower, int24 tickUpper) {
        tickLower = TickMath.minUsableTick(TICK_SPACING);
        // P_start = seededSupply / startFdvEth (HDX per ETH ved gulvet).
        uint256 ratioX192 = FullMath.mulDiv(seededSupply, 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 explorer2763msChain node87ms