Factory

0xb28c17b72538741590a3374127f84f79b59bbf30

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
17,142 bytes
ABI entries
24
9 read · 5 write
License
none

Contract information

Address
0xb28c17b72538741590a3374127f84f79b59bbf30
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x479a7f0928…3c6e7736f9

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (9)

allCurves(uint256)address
createFee()uint256
getCurve(address)address
lpFeeCollector()address
owner()address
pendingFees()uint256
positionManager()address
totalTokens()uint256
weth()address

Events (4)

CreateFeeUpdatedFeesWithdrawnOwnershipTransferredTokenCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_positionManager",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_lpFeeCollector",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "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": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "oldFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "CreateFeeUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "FeesWithdrawn",
    "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": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "curve",
        "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": "index",
        "type": "uint256"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "allCurves",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "createFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "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": "tokenAddr",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "curveAddr",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "getCurve",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lpFeeCollector",
    "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": "pendingFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "setCreateFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalTokens",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address payable",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "withdrawFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./Token.sol";
import "./BondingCurve.sol";
import "./interfaces/ILpFeeCollector.sol";

/**
 * @title Factory
 * @notice One-call fair-launch launchpad for the Robinhood Chain.
 *
 * Usage:
 *   factory.createToken("My Meme", "MEME", "ipfs://Qm...")
 *   → deploys Token + BondingCurve, seeds the curve with the full token supply.
 *
 * Revenue:
 *   • Creation fee (msg.value ≥ createFee) collected on every token launch.
 *   • All 1 % protocol fees from BondingCurve trades arrive here as native ETH.
 *   The owner calls withdrawFees() to collect everything.
 *   • Post-graduation: if `lpFeeCollector` is set, each deployed BondingCurve is
 *     automatically onboarded as a minter, so its graduation LP NFT goes to the
 *     collector instead of 0xdead — Uniswap V3 swap fees keep flowing (70/30 creator/protocol
 *     for bonding launches) to the
 *     creator and the protocol forever after. See LpFeeCollector.sol.
 */
contract Factory is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    // ─── Immutables ───────────────────────────────────────────────────────────

    address public immutable positionManager;
    address public immutable weth;
    /// address(0) preserves the legacy behaviour (graduation LP NFT burned to 0xdead).
    address public immutable lpFeeCollector;

    // ─── State ────────────────────────────────────────────────────────────────

    /// All deployed bonding curves
    address[] public allCurves;

    /// token address → bonding curve address
    mapping(address => address) public getCurve;

    /// Accumulated protocol fees (ETH) waiting to be withdrawn
    uint256 public pendingFees;

    /// Fee required to call createToken() — adjustable by owner (default 0.002 ETH)
    uint256 public createFee = 0.0002 ether;

    // ─── Events ───────────────────────────────────────────────────────────────

    event TokenCreated(
        address indexed creator,
        address indexed token,
        address indexed curve,
        string  name,
        string  symbol,
        string  metadataURI,
        uint256 index
    );
    event FeesWithdrawn(address indexed to, uint256 amount);
    event CreateFeeUpdated(uint256 oldFee, uint256 newFee);

    // ─── Constructor ──────────────────────────────────────────────────────────

    /**
     * @param _positionManager Uniswap V3 NonfungiblePositionManager address
     * @param _weth            WETH9 address on this chain
     * @param _lpFeeCollector  Shared LpFeeCollector address, or address(0) to keep the
     *                         legacy "burn LP NFT to 0xdead" behaviour on graduation.
     *                         This Factory must be allow-listed via
     *                         `LpFeeCollector.setFactory(address(this), true)` for the
     *                         onboarding call in createToken() to succeed.
     */
    constructor(address _positionManager, address _weth, address _lpFeeCollector) Ownable(msg.sender) {
        require(_positionManager != address(0), "Factory: zero positionManager");
        require(_weth            != address(0), "Factory: zero weth");
        positionManager = _positionManager;
        weth            = _weth;
        lpFeeCollector  = _lpFeeCollector;
    }

    // ─── Owner controls ──────────────────────────────────────────────────────

    /**
     * @notice Update the creation fee. Set to 0 to make launches free.
     * @param newFee New fee in wei (e.g. 0.0002 ether)
     */
    function setCreateFee(uint256 newFee) external onlyOwner {
        emit CreateFeeUpdated(createFee, newFee);
        createFee = newFee;
    }

    // ─── Launch ───────────────────────────────────────────────────────────────

    /**
     * @notice Deploy a new fair-launch token and its bonding curve.
     * @dev msg.value must be >= createFee.
     *      Any ETH above createFee is used as an instant initial buy for the creator.
     *      Bought tokens are forwarded to msg.sender in the same transaction.
     * @param name        ERC20 name
     * @param symbol      ERC20 symbol (ticker)
     * @param metadataURI IPFS or HTTPS URI for logo/description JSON
     * @return tokenAddr  Address of the deployed Token
     * @return curveAddr  Address of the deployed BondingCurve
     */
    function createToken(
        string calldata name,
        string calldata symbol,
        string calldata metadataURI
    ) external payable returns (address tokenAddr, address curveAddr) {
        require(msg.value >= createFee, "Factory: insufficient creation fee");
        // Only the fixed creation fee enters the fee pool; the rest goes to the initial buy
        pendingFees += createFee;
        uint256 initialBuyEth = msg.value - createFee;

        // 1. Deploy Token — mints 1 B tokens to this Factory
        Token  t = new Token(name, symbol, metadataURI);
        tokenAddr = address(t);

        // 2. Deploy BondingCurve (factory = msg.sender of the curve constructor)
        BondingCurve curve = new BondingCurve(
            tokenAddr,
            msg.sender,      // creator
            positionManager,
            weth,
            lpFeeCollector
        );
        curveAddr = address(curve);

        // Onboard the new curve as a minter so it can register its own graduation
        // position later on — atomic with deployment, no per-token owner action needed.
        if (lpFeeCollector != address(0)) {
            ILpFeeCollector(lpFeeCollector).onboardMinter(curveAddr);
        }

        // 3. Transfer entire supply to the BondingCurve
        IERC20(tokenAddr).safeTransfer(curveAddr, t.MAX_SUPPLY());

        // 4. Register
        getCurve[tokenAddr] = curveAddr;
        allCurves.push(curveAddr);

        emit TokenCreated(
            msg.sender,
            tokenAddr,
            curveAddr,
            name,
            symbol,
            metadataURI,
            allCurves.length - 1
        );

        // 5. Optional initial buy — excess ETH buys tokens for the creator instantly
        if (initialBuyEth > 0) {
            curve.buy{value: initialBuyEth}(0);
            // curve.buy sends tokens to address(this); forward them to the creator
            uint256 received = IERC20(tokenAddr).balanceOf(address(this));
            if (received > 0) {
                IERC20(tokenAddr).safeTransfer(msg.sender, received);
            }
        }
    }

    // ─── Fee management ───────────────────────────────────────────────────────

    /**
     * @notice Withdraw all accumulated protocol fees to `to`.
     * @param to Recipient address
     */
    function withdrawFees(address payable to) external onlyOwner nonReentrant {
        require(to != address(0), "Factory: zero recipient");
        uint256 amount = pendingFees;
        require(amount > 0, "Factory: no fees");
        pendingFees = 0;
        (bool ok,) = to.call{value: amount}("");
        require(ok, "Factory: ETH transfer failed");
        emit FeesWithdrawn(to, amount);
    }

    // ─── View ─────────────────────────────────────────────────────────────────

    function totalTokens() external view returns (uint256) {
        return allCurves.length;
    }

    // ─── Receive ETH fees from BondingCurves ─────────────────────────────────

    receive() external payable {
        pendingFees += msg.value;
    }
}
Chain explorer887msChain node90ms