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
- Creator
- 0xa9C92F6d21…afa28bB8Ed
- 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