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
- Creator
- 0x76053CcE1B…c8949D3385
- 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