DirectFactory

0xb76bd3a49e46399df323dc06bebcee2dbd346230

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
11,447 bytes
ABI entries
37
20 read · 5 write
License
none

Contract information

Address
0xb76bd3a49e46399df323dc06bebcee2dbd346230
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x8371a04200…8e94840e0d

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (20)

LOCK_ADDRESS()address
START_FDV()uint256
SWAP_ROUTER()address
TICK_CEIL()int24
TICK_FLOOR()int24
TICK_SPACING()int24
TOP_FDV()uint256
TOTAL_SUPPLY()uint256
UNISWAP_FEE()uint24
allTokens(uint256)address
createFee()uint256
getCreator(address)address
getPool(address)address
launchFdvWei()uint256
lpFeeCollector()address
owner()address
pendingFees()uint256
positionManager()address
totalTokens()uint256
weth()address

Events (4)

CreateFeeUpdatedFeesWithdrawnOwnershipTransferredTokenLaunched

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": "PoolAlreadyExists",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnexpectedPoolPrice",
    "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": "pool",
        "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"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "initialEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "TokenLaunched",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "LOCK_ADDRESS",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "START_FDV",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SWAP_ROUTER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_CEIL",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_FLOOR",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOP_FDV",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOTAL_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "UNISWAP_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "allTokens",
    "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": "pool",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "getCreator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "getPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchFdvWei",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "pure",
    "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 "./interfaces/INonfungiblePositionManager.sol";
import "./interfaces/IWETH.sol";
import "./interfaces/ILpFeeCollector.sol";
import "./interfaces/IUniswapV3SwapRouter.sol";
import "./libraries/V3PoolGuard.sol";
import "./libraries/TickMath.sol";

/**
 * @title DirectFactory
 * @notice Instant Uniswap V3 pool launch — single-sided token liquidity (no ETH seed).
 *
 * How it works:
 *   1. Creator calls createToken() with msg.value >= createFee.
 *   2. A new ERC20 Token is deployed (1B tokens minted to this factory).
 *   3. A Uniswap V3 1% pool is created at a fixed open FDV (~3 ETH).
 *   4. The entire supply is minted as single-sided LP (~3 → ~525 ETH FDV).
 *   5. Optional: surplus ETH (msg.value - createFee) buys tokens from the pool
 *      for the creator (first buy at launch).
 *   6. LP NFT → LpFeeCollector (90/10 fees) or 0xdead fallback.
 */
contract DirectFactory is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;

    /// Uniswap V3 1% fee tier (tick spacing = 200)
    uint24 public constant UNISWAP_FEE = 10_000;
    int24  public constant TICK_SPACING = 200;

    /// Target open / ceiling FDV (ETH wei). Aligned ticks ≈ 3.02 → 525 ETH.
    uint256 public constant START_FDV = 3 ether;
    uint256 public constant TOP_FDV = 530 ether;

    /// Precomputed spacing-aligned ticks (token0 convention: price = WETH/token).
    /// floor ≈ 3 ETH FDV, ceil ≈ 525 ETH FDV for 1B supply.
    int24 public constant TICK_FLOOR = -196_200;
    int24 public constant TICK_CEIL  = -144_600;

    address public constant LOCK_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    /// Uniswap V3 SwapRouter on Robinhood Chain (exactInputSingle payable / WETH).
    address public constant SWAP_ROUTER = 0xCaf681a66D020601342297493863E78C959E5cb2;

    address public immutable positionManager;
    address public immutable weth;
    address public immutable lpFeeCollector;

    uint256 public createFee = 0.0002 ether;
    uint256 public pendingFees;

    address[] public allTokens;
    mapping(address => address) public getPool;
    mapping(address => address) public getCreator;

    event TokenLaunched(
        address indexed creator,
        address indexed token,
        address indexed pool,
        string  name,
        string  symbol,
        string  metadataURI,
        uint256 index,
        uint256 initialEth,
        uint256 tokenId
    );
    event FeesWithdrawn(address indexed to, uint256 amount);
    event CreateFeeUpdated(uint256 oldFee, uint256 newFee);

    constructor(address _positionManager, address _weth, address _lpFeeCollector) Ownable(msg.sender) {
        require(_positionManager != address(0), "DirectFactory: zero positionManager");
        require(_weth != address(0), "DirectFactory: zero weth");
        require(TICK_FLOOR % TICK_SPACING == 0, "DirectFactory: bad floor");
        require(TICK_CEIL % TICK_SPACING == 0, "DirectFactory: bad ceil");
        require(TICK_FLOOR < TICK_CEIL, "DirectFactory: bad range");
        require(START_FDV < TOP_FDV, "DirectFactory: bad fdv");
        positionManager = _positionManager;
        weth = _weth;
        lpFeeCollector = _lpFeeCollector;
    }

    function setCreateFee(uint256 newFee) external onlyOwner {
        emit CreateFeeUpdated(createFee, newFee);
        createFee = newFee;
    }

    /**
     * @notice Deploy token + single-sided Uniswap V3 LP. No ETH seed liquidity required.
     * @dev msg.value must be >= createFee. Surplus (if any) buys tokens for the creator.
     *      `initialEth` in TokenLaunched is the buy amount (surplus), not LP seed.
     */
    function createToken(
        string calldata name,
        string calldata symbol,
        string calldata metadataURI
    ) external payable nonReentrant returns (address tokenAddr, address pool) {
        require(msg.value >= createFee, "DirectFactory: insufficient createFee");

        pendingFees += createFee;
        uint256 buyEth = msg.value - createFee;

        tokenAddr = address(new Token(name, symbol, metadataURI));

        uint256 tokenId;
        (pool, tokenId) = _deployPool(tokenAddr, msg.sender);

        getPool[tokenAddr] = pool;
        getCreator[tokenAddr] = msg.sender;
        allTokens.push(tokenAddr);

        if (buyEth > 0) {
            _buyTokens(tokenAddr, msg.sender, buyEth);
        }

        emit TokenLaunched(
            msg.sender,
            tokenAddr,
            pool,
            name,
            symbol,
            metadataURI,
            allTokens.length - 1,
            buyEth,
            tokenId
        );
    }

    function _deployPool(address tokenAddr, address creator)
        internal
        returns (address pool, uint256 tokenId)
    {
        bool tokenIs0 = tokenAddr < weth;
        (int24 tickLower, int24 tickUpper, int24 initTick) = _rangeFor(tokenIs0);
        uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(initTick);

        (address token0, address token1) = tokenIs0 ? (tokenAddr, weth) : (weth, tokenAddr);

        V3PoolGuard.requireNoExistingPool(positionManager, token0, token1, UNISWAP_FEE);
        pool = INonfungiblePositionManager(positionManager).createAndInitializePoolIfNecessary(
            token0, token1, UNISWAP_FEE, sqrtPriceX96
        );
        V3PoolGuard.requireExactSqrtPrice(pool, sqrtPriceX96);

        tokenId = _mintSingleSided(tokenAddr, tokenIs0, tickLower, tickUpper, creator);
    }

    function _rangeFor(bool tokenIs0)
        internal
        pure
        returns (int24 tickLower, int24 tickUpper, int24 initTick)
    {
        if (tokenIs0) {
            return (TICK_FLOOR, TICK_CEIL, TICK_FLOOR);
        }
        return (-TICK_CEIL, -TICK_FLOOR, -TICK_FLOOR);
    }

    function _mintSingleSided(
        address tokenAddr,
        bool tokenIs0,
        int24 tickLower,
        int24 tickUpper,
        address creator
    ) internal returns (uint256 tokenId) {
        uint256 supply = IERC20(tokenAddr).balanceOf(address(this));
        require(supply == TOTAL_SUPPLY, "DirectFactory: bad supply");

        IERC20(tokenAddr).forceApprove(positionManager, supply);

        address mintRecipient = lpFeeCollector != address(0) ? lpFeeCollector : LOCK_ADDRESS;
        tokenId = _mintLp(tokenAddr, tokenIs0, tickLower, tickUpper, supply, mintRecipient);

        if (lpFeeCollector != address(0)) {
            ILpFeeCollector(lpFeeCollector).registerPosition(tokenId, creator);
        }

        uint256 left = IERC20(tokenAddr).balanceOf(address(this));
        if (left > 0) {
            IERC20(tokenAddr).safeTransfer(creator, left);
        }
    }

    function _mintLp(
        address tokenAddr,
        bool tokenIs0,
        int24 tickLower,
        int24 tickUpper,
        uint256 supply,
        address mintRecipient
    ) internal returns (uint256 tokenId) {
        (address token0, address token1) = tokenIs0 ? (tokenAddr, weth) : (weth, tokenAddr);
        uint256 amount0Desired = tokenIs0 ? supply : 0;
        uint256 amount1Desired = tokenIs0 ? 0 : supply;

        uint128 liquidity;
        uint256 used0;
        uint256 used1;
        (tokenId, liquidity, used0, used1) = INonfungiblePositionManager(positionManager).mint(
            INonfungiblePositionManager.MintParams({
                token0: token0,
                token1: token1,
                fee: UNISWAP_FEE,
                tickLower: tickLower,
                tickUpper: tickUpper,
                amount0Desired: amount0Desired,
                amount1Desired: amount1Desired,
                amount0Min: 0,
                amount1Min: 0,
                recipient: mintRecipient,
                deadline: block.timestamp
            })
        );

        require(liquidity > 0, "DirectFactory: zero liquidity");
        uint256 wethUsed = tokenIs0 ? used1 : used0;
        require(wethUsed == 0, "DirectFactory: not single-sided");
        uint256 tokenUsed = tokenIs0 ? used0 : used1;
        require(tokenUsed >= supply - supply / 1000, "DirectFactory: seed incomplete");
    }

    /// @dev Swap ETH → token via Uniswap V3; tokens sent to `to`.
    function _buyTokens(address tokenAddr, address to, uint256 ethIn) internal {
        uint256 out = IUniswapV3SwapRouter(SWAP_ROUTER).exactInputSingle{value: ethIn}(
            IUniswapV3SwapRouter.ExactInputSingleParams({
                tokenIn: weth,
                tokenOut: tokenAddr,
                fee: UNISWAP_FEE,
                recipient: to,
                amountIn: ethIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            })
        );
        require(out > 0, "DirectFactory: buy failed");
    }

    function withdrawFees(address payable to) external onlyOwner nonReentrant {
        require(to != address(0), "DirectFactory: zero recipient");
        uint256 amount = pendingFees;
        require(amount > 0, "DirectFactory: no fees");
        pendingFees = 0;
        (bool ok,) = to.call{value: amount}("");
        require(ok, "DirectFactory: ETH transfer failed");
        emit FeesWithdrawn(to, amount);
    }

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

    /// @notice Open FDV used for pool init (ETH wei), from aligned start tick.
    function launchFdvWei() external pure returns (uint256) {
        return START_FDV;
    }

    receive() external payable {
        pendingFees += msg.value;
    }
}
Chain explorer4383msChain node80ms