Factory
0xde73851e91decee06e55cab2366d0c28666fb9be
Verification
Unverified
v0.8.28+commit.7893614a
Type
Contract
23,742 bytes
ABI entries
46
17 read · 7 write
License
none
Contract information
- Address
- 0xde73851e91decee06e55cab2366d0c28666fb9be
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x4DA7cEB08b…6B0DeAe59c
- Creation tx
- 0x69e430b81e…1a3d573110
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (17)
MAX_CREATION_FEE() → uint256
MAX_CREATOR_REWARD() → uint256
MAX_CREATOR_WINDOW() → uint64
MAX_FEE_BPS() → uint256
MAX_GRADUATION_BUDGET() → uint256
MAX_GRADUATION_FEE() → uint256
creationFeeWei() → uint256
currentConfig() → uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint96, uint96, address, address, uint64
deployerProxy() → address
getCurrentConfig() → tuple
isBondingCurve(address) → bool
owner() → address
pendingCurve() → address
pendingOwner() → address
pendingURI() → string
predictAddresses(string, string, address, bytes32, bytes32) → address, address
predictAddressesWithMode(string, string, address, bool, bool, bytes32, bytes32) → address, address
Events (6)
ConfigUpdatedCreationFeeUpdatedDeployerProxyChangedOwnershipTransferStartedOwnershipTransferredTokenCreated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
},
{
"components": [
{
"internalType": "uint256",
"name": "virtualTokenReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "curveSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "migrationSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationEthTarget",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationFeeWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "creatorGraduationRewardWei",
"type": "uint256"
},
{
"internalType": "uint96",
"name": "protocolFeeBps",
"type": "uint96"
},
{
"internalType": "uint96",
"name": "creatorFeeBps",
"type": "uint96"
},
{
"internalType": "address",
"name": "protocolFeeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "migrator",
"type": "address"
},
{
"internalType": "uint64",
"name": "creatorFirstBuyWindowSec",
"type": "uint64"
}
],
"internalType": "struct IFactory.CurveConfig",
"name": "cfg",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "creationFeeWei_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "expected",
"type": "address"
},
{
"internalType": "address",
"name": "actual",
"type": "address"
}
],
"name": "AddressMismatch",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "expected",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "actual",
"type": "uint256"
}
],
"name": "CreationFeeMismatch",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wei_",
"type": "uint256"
}
],
"name": "CreationFeeTooHigh",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wei_",
"type": "uint256"
}
],
"name": "CreatorRewardTooHigh",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint64",
"name": "sec_",
"type": "uint64"
}
],
"name": "CreatorWindowTooLong",
"type": "error"
},
{
"inputs": [],
"name": "FeeTransferFailed",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wei_",
"type": "uint256"
}
],
"name": "GraduationBudgetTooHigh",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wei_",
"type": "uint256"
}
],
"name": "GraduationFeeTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "InvalidConfig",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "InvalidCreator",
"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": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "bps",
"type": "uint256"
}
],
"name": "TotalFeeTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "virtualTokenReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "curveSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "migrationSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationEthTarget",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationFeeWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "creatorGraduationRewardWei",
"type": "uint256"
},
{
"internalType": "uint96",
"name": "protocolFeeBps",
"type": "uint96"
},
{
"internalType": "uint96",
"name": "creatorFeeBps",
"type": "uint96"
},
{
"internalType": "address",
"name": "protocolFeeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "migrator",
"type": "address"
},
{
"internalType": "uint64",
"name": "creatorFirstBuyWindowSec",
"type": "uint64"
}
],
"indexed": false,
"internalType": "struct IFactory.CurveConfig",
"name": "newConfig",
"type": "tuple"
}
],
"name": "ConfigUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "newFee",
"type": "uint256"
}
],
"name": "CreationFeeUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldProxy",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newProxy",
"type": "address"
}
],
"name": "DeployerProxyChanged",
"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": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "bondingCurve",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
}
],
"name": "TokenCreated",
"type": "event"
},
{
"inputs": [],
"name": "MAX_CREATION_FEE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_CREATOR_REWARD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_CREATOR_WINDOW",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_GRADUATION_BUDGET",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_GRADUATION_FEE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"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": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "metadataURI",
"type": "string"
},
{
"internalType": "bool",
"name": "cashbackMode",
"type": "bool"
},
{
"internalType": "bool",
"name": "creatorFirstBuy",
"type": "bool"
},
{
"internalType": "bytes32",
"name": "tokenSalt",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "curveSalt",
"type": "bytes32"
}
],
"name": "createToken",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "creationFeeWei",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "currentConfig",
"outputs": [
{
"internalType": "uint256",
"name": "virtualTokenReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "curveSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "migrationSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationEthTarget",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationFeeWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "creatorGraduationRewardWei",
"type": "uint256"
},
{
"internalType": "uint96",
"name": "protocolFeeBps",
"type": "uint96"
},
{
"internalType": "uint96",
"name": "creatorFeeBps",
"type": "uint96"
},
{
"internalType": "address",
"name": "protocolFeeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "migrator",
"type": "address"
},
{
"internalType": "uint64",
"name": "creatorFirstBuyWindowSec",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "deployerProxy",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentConfig",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "virtualTokenReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "curveSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "migrationSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationEthTarget",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationFeeWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "creatorGraduationRewardWei",
"type": "uint256"
},
{
"internalType": "uint96",
"name": "protocolFeeBps",
"type": "uint96"
},
{
"internalType": "uint96",
"name": "creatorFeeBps",
"type": "uint96"
},
{
"internalType": "address",
"name": "protocolFeeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "migrator",
"type": "address"
},
{
"internalType": "uint64",
"name": "creatorFirstBuyWindowSec",
"type": "uint64"
}
],
"internalType": "struct IFactory.CurveConfig",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "curve",
"type": "address"
}
],
"name": "isBondingCurve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingCurve",
"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": "pendingURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "bytes32",
"name": "tokenSalt",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "curveSalt",
"type": "bytes32"
}
],
"name": "predictAddresses",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "bool",
"name": "cashbackMode",
"type": "bool"
},
{
"internalType": "bool",
"name": "creatorFirstBuy",
"type": "bool"
},
{
"internalType": "bytes32",
"name": "tokenSalt",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "curveSalt",
"type": "bytes32"
}
],
"name": "predictAddressesWithMode",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "virtualTokenReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserves",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "curveSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "migrationSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationEthTarget",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "graduationFeeWei",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "creatorGraduationRewardWei",
"type": "uint256"
},
{
"internalType": "uint96",
"name": "protocolFeeBps",
"type": "uint96"
},
{
"internalType": "uint96",
"name": "creatorFeeBps",
"type": "uint96"
},
{
"internalType": "address",
"name": "protocolFeeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "migrator",
"type": "address"
},
{
"internalType": "uint64",
"name": "creatorFirstBuyWindowSec",
"type": "uint64"
}
],
"internalType": "struct IFactory.CurveConfig",
"name": "cfg",
"type": "tuple"
}
],
"name": "setConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wei_",
"type": "uint256"
}
],
"name": "setCreationFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "proxy",
"type": "address"
}
],
"name": "setDeployerProxy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import { Ownable, Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { BondingCurve } from "./BondingCurve.sol";
import { Token } from "./Token.sol";
import { IFactory } from "./interfaces/IFactory.sol";
/// @title Factory
/// @notice Deploys (Token, BondingCurve) pairs at CREATE2-mineable addresses.
contract Factory is IFactory, Ownable2Step, ReentrancyGuardTransient {
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error ZeroAddress();
error TotalFeeTooHigh(uint256 bps);
error GraduationFeeTooHigh(uint256 wei_);
error CreatorRewardTooHigh(uint256 wei_);
error GraduationBudgetTooHigh(uint256 wei_);
error CreationFeeTooHigh(uint256 wei_);
error CreationFeeMismatch(uint256 expected, uint256 actual);
error CreatorWindowTooLong(uint64 sec_);
error InvalidConfig();
error AddressMismatch(address expected, address actual);
error FeeTransferFailed();
error InvalidCreator(address creator);
/* -------------------------------------------------------------------------- */
/* CONSTANTS */
/* -------------------------------------------------------------------------- */
uint256 public constant MAX_FEE_BPS = 200;
uint256 public constant MAX_GRADUATION_FEE = 1 ether;
uint256 public constant MAX_CREATOR_REWARD = 1 ether;
uint256 public constant MAX_GRADUATION_BUDGET = 1.5 ether;
uint256 public constant MAX_CREATION_FEE = 0.01 ether;
uint64 public constant MAX_CREATOR_WINDOW = 1 hours;
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
CurveConfig public currentConfig;
/// @inheritdoc IFactory
uint256 public override creationFeeWei;
/// @inheritdoc IFactory
mapping(address curve => bool) public override isBondingCurve;
/// @inheritdoc IFactory
/// @dev Plain storage (not transient): the token reads via staticcall from its own
/// constructor's namespace, where transient storage is unavailable.
address public override pendingCurve;
/// @inheritdoc IFactory
string public override pendingURI;
/// @inheritdoc IFactory
address public override deployerProxy;
/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR */
/* -------------------------------------------------------------------------- */
constructor(address owner_, CurveConfig memory cfg, uint256 creationFeeWei_) Ownable(owner_) {
_setConfig(cfg);
_setCreationFee(creationFeeWei_);
}
/* -------------------------------------------------------------------------- */
/* ADMIN ACTIONS */
/* -------------------------------------------------------------------------- */
/// @inheritdoc IFactory
function setConfig(CurveConfig calldata cfg) external override onlyOwner {
_setConfig(cfg);
}
/// @inheritdoc IFactory
function setCreationFee(uint256 wei_) external override onlyOwner {
_setCreationFee(wei_);
}
/// @inheritdoc IFactory
/// @dev Authorizes `proxy` to call {IToken.setEnsReverseName} on every token deployed
/// by this factory. Zero clears the authorization (only the creator path remains).
/// Scope of rotation:
/// - Tokens whose single-shot `ensReverseSet` slot is ALREADY burned are
/// immutable; no caller — old, new, or none — can set the reverse name.
/// - Tokens with an UNCLAIMED slot (e.g. deployed via direct `createToken`
/// without the atomic proxy) have their reverse name settable by whichever
/// address currently occupies this slot. A malicious owner could therefore
/// race those tokens' creators to claim a bogus reverse name; ENS-aware
/// wallets reject the display via forward-validation, so the worst outcome
/// is a visible but non-resolving reverse record — never fund loss.
/// Atomic-proxy deploys burn the slot in the same tx as the token creation, so
/// they have no race window.
function setDeployerProxy(address proxy) external override onlyOwner {
address old = deployerProxy;
deployerProxy = proxy;
emit DeployerProxyChanged(old, proxy);
}
/* -------------------------------------------------------------------------- */
/* PUBLIC ACTIONS */
/* -------------------------------------------------------------------------- */
/// @inheritdoc IFactory
function createToken(
string calldata name,
string calldata symbol,
address creator,
string calldata metadataURI,
bool cashbackMode,
bool creatorFirstBuy,
bytes32 tokenSalt,
bytes32 curveSalt
) external payable override nonReentrant returns (address token, address curve) {
if (creator == address(0)) revert ZeroAddress();
if (msg.value != creationFeeWei) revert CreationFeeMismatch(creationFeeWei, msg.value);
CurveConfig memory cfg = currentConfig;
// Reject creators that would brick the pull-payment claim path: this contract has
// no `receive()`, and the migrator has no payout path either.
if (creator == address(this) || creator == cfg.migrator) revert InvalidCreator(creator);
(token, curve) =
_deployPair(name, symbol, creator, metadataURI, cashbackMode, creatorFirstBuy, cfg, tokenSalt, curveSalt);
isBondingCurve[curve] = true;
emit TokenCreated(token, creator, curve, name, symbol);
if (msg.value != 0) {
// Bounded-gas forward: a malicious recipient can't brick createToken.
(bool ok,) = payable(cfg.protocolFeeRecipient).call{ value: msg.value, gas: 30_000 }("");
if (!ok) revert FeeTransferFailed();
}
}
/* -------------------------------------------------------------------------- */
/* VIEWS */
/* -------------------------------------------------------------------------- */
/// @inheritdoc IFactory
function getCurrentConfig() external view override returns (CurveConfig memory) {
return currentConfig;
}
/// @inheritdoc IFactory
function predictAddresses(
string calldata name,
string calldata symbol,
address creator,
bytes32 tokenSalt,
bytes32 curveSalt
) external view override returns (address token, address curve) {
return _predictAddresses(name, symbol, creator, false, false, currentConfig, tokenSalt, curveSalt);
}
/// @notice Variant of {predictAddresses} that accounts for the launch flags. Both
/// `cashbackMode` and `creatorFirstBuy` enter the curve's CREATE2 init-code hash.
function predictAddressesWithMode(
string calldata name,
string calldata symbol,
address creator,
bool cashbackMode,
bool creatorFirstBuy,
bytes32 tokenSalt,
bytes32 curveSalt
) external view returns (address token, address curve) {
return _predictAddresses(
name, symbol, creator, cashbackMode, creatorFirstBuy, currentConfig, tokenSalt, curveSalt
);
}
/* -------------------------------------------------------------------------- */
/* INTERNAL HELPERS */
/* -------------------------------------------------------------------------- */
function _deployPair(
string calldata name,
string calldata symbol,
address creator,
string calldata metadataURI,
bool cashbackMode,
bool creatorFirstBuy,
CurveConfig memory cfg,
bytes32 tokenSalt,
bytes32 curveSalt
) private returns (address token, address curve) {
(address predictedToken, address predictedCurve) = _predictAddresses(
name, symbol, creator, cashbackMode, creatorFirstBuy, cfg, tokenSalt, curveSalt
);
BondingCurve c = new BondingCurve{ salt: curveSalt }(
_initParams(predictedToken, creator, cashbackMode, creatorFirstBuy, cfg)
);
if (address(c) != predictedCurve) revert AddressMismatch(predictedCurve, address(c));
pendingCurve = address(c);
pendingURI = metadataURI;
// Mix `creator` into the effective token salt so two creators can share
// (name, symbol, tokenSalt) without colliding at the same CREATE2 address.
bytes32 effectiveTokenSalt = keccak256(abi.encode(tokenSalt, creator));
Token t = new Token{ salt: effectiveTokenSalt }(name, symbol, address(this));
pendingCurve = address(0);
delete pendingURI;
if (address(t) != predictedToken) revert AddressMismatch(predictedToken, address(t));
token = address(t);
curve = address(c);
}
function _initParams(
address token,
address creator,
bool cashbackMode,
bool creatorFirstBuy,
CurveConfig memory cfg
) private view returns (BondingCurve.InitParams memory) {
return BondingCurve.InitParams({
token: token,
creator: creator,
factory: address(this),
protocolFeeRecipient: cfg.protocolFeeRecipient,
migrator: cfg.migrator,
virtualTokenReserves: cfg.virtualTokenReserves,
virtualEthReserves: cfg.virtualEthReserves,
curveSupply: cfg.curveSupply,
migrationSupply: cfg.migrationSupply,
graduationEthTarget: cfg.graduationEthTarget,
graduationFeeWei: cfg.graduationFeeWei,
creatorGraduationRewardWei: cfg.creatorGraduationRewardWei,
protocolFeeBps: cfg.protocolFeeBps,
creatorFeeBps: cfg.creatorFeeBps,
cashbackMode: cashbackMode,
creatorWindowDuration: creatorFirstBuy ? cfg.creatorFirstBuyWindowSec : 0
});
}
function _predictAddresses(
string calldata name,
string calldata symbol,
address creator,
bool cashbackMode,
bool creatorFirstBuy,
CurveConfig memory cfg,
bytes32 tokenSalt,
bytes32 curveSalt
) private view returns (address token, address curve) {
bytes32 tokenInitHash = keccak256(
abi.encodePacked(type(Token).creationCode, abi.encode(name, symbol, address(this)))
);
bytes32 effectiveTokenSalt = keccak256(abi.encode(tokenSalt, creator));
token = _create2Address(effectiveTokenSalt, tokenInitHash);
bytes32 curveInitHash = keccak256(
abi.encodePacked(
type(BondingCurve).creationCode,
abi.encode(_initParams(token, creator, cashbackMode, creatorFirstBuy, cfg))
)
);
curve = _create2Address(curveSalt, curveInitHash);
}
function _create2Address(bytes32 salt, bytes32 initHash) private view returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initHash)))));
}
function _setConfig(CurveConfig memory cfg) private {
if (
cfg.protocolFeeRecipient == address(0) || cfg.migrator == address(0) || cfg.virtualTokenReserves == 0
|| cfg.virtualEthReserves == 0 || cfg.curveSupply == 0 || cfg.migrationSupply == 0
|| cfg.graduationEthTarget == 0
) revert InvalidConfig();
if (cfg.virtualTokenReserves < cfg.curveSupply) revert InvalidConfig();
// Curve must hold enough tokens to cover both the curve sale and the migration seed
// (mirrors `Token.TOTAL_SUPPLY` to avoid a deployed-instance read at config time).
if (cfg.curveSupply + cfg.migrationSupply > 1_000_000_000 * 1e18) revert InvalidConfig();
uint256 totalBps = uint256(cfg.protocolFeeBps) + uint256(cfg.creatorFeeBps);
if (totalBps > MAX_FEE_BPS) revert TotalFeeTooHigh(totalBps);
if (cfg.graduationFeeWei > MAX_GRADUATION_FEE) revert GraduationFeeTooHigh(cfg.graduationFeeWei);
if (cfg.creatorGraduationRewardWei > MAX_CREATOR_REWARD) {
revert CreatorRewardTooHigh(cfg.creatorGraduationRewardWei);
}
uint256 gradBudget = cfg.graduationFeeWei + cfg.creatorGraduationRewardWei;
if (gradBudget > MAX_GRADUATION_BUDGET) revert GraduationBudgetTooHigh(gradBudget);
if (gradBudget >= cfg.graduationEthTarget) revert InvalidConfig();
// Both graduation paths (ETH-target and supply-exhaust) must clear the budget.
// Curve invariant gives `ethAtSupplyCap = V_e * curveSupply / (V_t - curveSupply)`.
uint256 ethAtSupplyCap =
Math.mulDiv(cfg.virtualEthReserves, cfg.curveSupply, cfg.virtualTokenReserves - cfg.curveSupply);
if (ethAtSupplyCap < gradBudget) revert InvalidConfig();
if (cfg.creatorFirstBuyWindowSec > MAX_CREATOR_WINDOW) {
revert CreatorWindowTooLong(cfg.creatorFirstBuyWindowSec);
}
currentConfig = cfg;
emit ConfigUpdated(cfg);
}
function _setCreationFee(uint256 wei_) private {
if (wei_ > MAX_CREATION_FEE) revert CreationFeeTooHigh(wei_);
creationFeeWei = wei_;
emit CreationFeeUpdated(wei_);
}
}
Chain explorer2542msChain node145ms