Factory

0x82e927237ff6aaff00d0d9b60e77e495ac722799

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
23,742 bytes
ABI entries
46
17 read · 7 write
License
none

Contract information

Address
0x82e927237ff6aaff00d0d9b60e77e495ac722799
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0xbcd807a57e…6e58a86c14

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 explorer6328msChain node100ms