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
- Creator
- 0x31098c0E25…659D3783Fe
- 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