FactoryV4

0x08a95be2bc4a3dcf1488ebd4145004d5948297e9

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
3,973 bytes
ABI entries
39
17 read · 6 write
License
none

Contract information

Address
0x08a95be2bc4a3dcf1488ebd4145004d5948297e9
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x5032a6d180…9ab6ff0256

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (17)

MAX_FEE_BPS()uint16
creationFee()uint256
creatorOf(address)address
curveImpl()address
curveOf(address)address
feeBps()uint16
graduationFee()uint256
locker()address
owner()address
pendingOwner()address
positionManager()address
renounceOwnership()
tokenImpl()address
treasury()address
v3Fee()uint24
virtBaseInit()uint256
whype()address

Events (6)

ImplsUpdatedOwnershipTransferStartedOwnershipTransferredParamsUpdatedTokenCreatedTreasuryUpdated

ABI

[
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "tokenImpl",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "curveImpl",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "treasury",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "whype",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "locker",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "feeBps",
            "type": "uint16"
          },
          {
            "internalType": "uint24",
            "name": "v3Fee",
            "type": "uint24"
          },
          {
            "internalType": "uint256",
            "name": "virtBaseInit",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "graduationFee",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "creationFee",
            "type": "uint256"
          }
        ],
        "internalType": "struct FactoryV4.InitConfig",
        "name": "c",
        "type": "tuple"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BadCurveConfig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadV3Fee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FailedDeployment",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FeeTooHigh",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "target",
        "type": "address"
      }
    ],
    "name": "NoCode",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "tokenImpl",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "curveImpl",
        "type": "address"
      }
    ],
    "name": "ImplsUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "feeBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint24",
        "name": "v3Fee",
        "type": "uint24"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "virtBaseInit",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "graduationFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "creationFee",
        "type": "uint256"
      }
    ],
    "name": "ParamsUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "curve",
        "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": "timestamp",
        "type": "uint256"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      }
    ],
    "name": "TreasuryUpdated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_FEE_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "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": "createToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "curve",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creationFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "creatorOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "curveImpl",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "curveOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "graduationFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "locker",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "t",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "c",
        "type": "address"
      }
    ],
    "name": "setImpls",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "f",
        "type": "uint16"
      },
      {
        "internalType": "uint24",
        "name": "v3Fee_",
        "type": "uint24"
      },
      {
        "internalType": "uint256",
        "name": "v",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "g",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "c",
        "type": "uint256"
      }
    ],
    "name": "setParams",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "t",
        "type": "address"
      }
    ],
    "name": "setTreasury",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenImpl",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v3Fee",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "virtBaseInit",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "whype",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

interface ITokenInit {
    function initialize(string memory n, string memory s, address curve) external;
}

interface ICurveInit {
    function initialize(
        address t,
        address treasury,
        address positionManager,
        address whype,
        address locker,
        uint16 feeBps,
        uint24 v3Fee,
        uint256 virtBaseInit,
        uint256 graduationFee
    ) external;
    function buyTo(address to, uint256 minOut) external payable;
}

/// @title FactoryV4
/// @notice FactoryV3 verbatim (byte-identical economics, same InitConfig / createToken flow / immutable
/// V3 targets / Ownable2Step + renounce-disabled admin) with ONE addition on top of `creatorOf`: an
/// authoritative on-chain `curveOf[token]` binding recorded at createToken. This lets any client verify
/// a coin's (token, curve) pair came from THIS factory purely on-chain — no indexer/registry trust and
/// no dedicated-RPC dependence (closes the frontend provenance residual). It points at the LockerV4
/// PROXY as its immutable `locker`, so every coin this factory launches graduates into the single,
/// Safe-governed, upgradeable-economics locker — the last generation.
contract FactoryV4 is Ownable2Step {
    uint16 public constant MAX_FEE_BPS = 500; // 5% ceiling on bonding-curve trade fees

    address public tokenImpl;
    address public curveImpl;
    address public treasury;
    // V3 graduation targets are IMMUTABLE — they can never be silently repointed to steal graduated
    // LP NFTs / fees (audit HIGH). `locker` is the LockerV4 proxy. Redeploy the factory to change.
    address public immutable positionManager; // HyperSwap V3 NonfungiblePositionManager
    address public immutable whype; // wrapped HYPE
    address public immutable locker; // LockerV4 proxy — Safe-governed upgradeable economics
    uint16 public feeBps;
    uint24 public v3Fee; // V3 pool fee tier (10000 = 1%)
    uint256 public virtBaseInit;
    uint256 public graduationFee;
    uint256 public creationFee; // anti-spam; may be 0

    /// The coin's creator, recorded at createToken (immutable per coin). LockerV4 reads this to
    /// attribute the creator-fee split; anything not created here returns address(0).
    mapping(address => address) public creatorOf;
    /// NEW vs V3: the coin's canonical curve, recorded at createToken (immutable per coin). An
    /// authoritative on-chain token↔curve binding a client can check without trusting any indexer;
    /// anything not created here returns address(0).
    mapping(address => address) public curveOf;

    event TokenCreated(
        address indexed token,
        address indexed curve,
        address indexed creator,
        string name,
        string symbol,
        string metadataURI,
        uint256 timestamp
    );
    event ParamsUpdated(uint16 feeBps, uint24 v3Fee, uint256 virtBaseInit, uint256 graduationFee, uint256 creationFee);
    event TreasuryUpdated(address indexed treasury);
    event ImplsUpdated(address indexed tokenImpl, address indexed curveImpl);

    error ZeroAddress();
    error NoCode(address target);
    error FeeTooHigh();
    error BadV3Fee();
    error BadCurveConfig();

    struct InitConfig {
        address tokenImpl;
        address curveImpl;
        address treasury;
        address positionManager;
        address whype;
        address locker;
        uint16 feeBps;
        uint24 v3Fee;
        uint256 virtBaseInit;
        uint256 graduationFee;
        uint256 creationFee;
    }

    constructor(InitConfig memory c) Ownable(msg.sender) {
        _validateImpls(c.tokenImpl, c.curveImpl);
        _validateConfig(c.treasury, c.positionManager, c.whype, c.locker, c.feeBps, c.v3Fee, c.virtBaseInit);
        tokenImpl = c.tokenImpl;
        curveImpl = c.curveImpl;
        treasury = c.treasury;
        positionManager = c.positionManager;
        whype = c.whype;
        locker = c.locker;
        feeBps = c.feeBps;
        v3Fee = c.v3Fee;
        virtBaseInit = c.virtBaseInit;
        graduationFee = c.graduationFee;
        creationFee = c.creationFee;
    }

    function createToken(string calldata name, string calldata symbol, string calldata metadataURI)
        external
        payable
        returns (address token, address curve)
    {
        require(msg.value >= creationFee, "fee");
        token = Clones.clone(tokenImpl);
        curve = Clones.clone(curveImpl);

        ITokenInit(token).initialize(name, symbol, curve);
        ICurveInit(curve)
            .initialize(token, treasury, positionManager, whype, locker, feeBps, v3Fee, virtBaseInit, graduationFee);

        // Record the creator + the canonical curve BEFORE the emit + dev-buy so both are set the
        // instant the coin exists (a client can verify the (token,curve) binding on-chain immediately).
        creatorOf[token] = msg.sender;
        curveOf[token] = curve;

        // Emit BEFORE the optional dev-buy (indexer relies on TokenCreated having a lower log index
        // than the dev-buy's Buy event in the same tx). Same ordering as V1/V3.
        emit TokenCreated(token, curve, msg.sender, name, symbol, metadataURI, block.timestamp);

        if (creationFee > 0) {
            (bool ok,) = treasury.call{value: creationFee}("");
            require(ok, "feeXfer");
        }

        uint256 devBuy = msg.value - creationFee; // optional atomic dev buy (anti-snipe)
        if (devBuy > 0) ICurveInit(curve).buyTo{value: devBuy}(msg.sender, 0);
    }

    // ---------- admin (owner = Safe; SHOULD stay a multisig) ----------
    function setParams(uint16 f, uint24 v3Fee_, uint256 v, uint256 g, uint256 c) external onlyOwner {
        if (f > MAX_FEE_BPS) revert FeeTooHigh();
        if (v3Fee_ != 10000) revert BadV3Fee();
        if (v == 0) revert BadCurveConfig();
        feeBps = f;
        v3Fee = v3Fee_;
        virtBaseInit = v;
        graduationFee = g;
        creationFee = c;
        emit ParamsUpdated(f, v3Fee_, v, g, c);
    }

    function setTreasury(address t) external onlyOwner {
        if (t == address(0)) revert ZeroAddress();
        treasury = t;
        emit TreasuryUpdated(t);
    }

    function setImpls(address t, address c) external onlyOwner {
        _validateImpls(t, c);
        tokenImpl = t;
        curveImpl = c;
        emit ImplsUpdated(t, c);
    }

    /// Disabled: renouncing would PERMANENTLY brick setParams/setImpls/setTreasury. Ownership can
    /// still be TRANSFERRED (two-step) to a new multisig; it just can't be dropped to address(0).
    function renounceOwnership() public view override onlyOwner {
        revert("renounce disabled");
    }

    function _validateConfig(
        address treasury_,
        address positionManager_,
        address whype_,
        address locker_,
        uint16 feeBps_,
        uint24 v3Fee_,
        uint256 virtBaseInit_
    ) internal view {
        if (treasury_ == address(0) || positionManager_ == address(0) || whype_ == address(0) || locker_ == address(0))
        {
            revert ZeroAddress();
        }
        _requireCode(positionManager_);
        _requireCode(whype_);
        _requireCode(locker_);
        if (feeBps_ > MAX_FEE_BPS) revert FeeTooHigh();
        if (v3Fee_ != 10000) revert BadV3Fee();
        if (virtBaseInit_ == 0) revert BadCurveConfig();
    }

    function _validateImpls(address tokenImpl_, address curveImpl_) internal view {
        if (tokenImpl_ == address(0) || curveImpl_ == address(0)) revert ZeroAddress();
        _requireCode(tokenImpl_);
        _requireCode(curveImpl_);
    }

    function _requireCode(address target) internal view {
        if (target.code.length == 0) revert NoCode(target);
    }
}
Chain explorer7393msChain node105ms