Factory
0x5a7d77d7743597c898bec89ffec210c93297b050
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
16,914 bytes
ABI entries
48
25 read · 12 write
License
none
Contract information
- Address
- 0x5a7d77d7743597c898bec89ffec210c93297b050
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0xF3c9dE602F…a87Ca00f58
- Creation tx
- 0xdbdfe05840…5c1eeb4d91
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (25)
CREATOR_SHARE_BPS() → uint256
POOL_MANAGER() → address
POSITION_MANAGER() → address
UNIVERSAL_ROUTER() → address
creatorFeesAccrued(address) → uint256
creatorFeesClaimed(address) → uint256
creatorTokens(address, uint256) → address
deployCoinEnabled() → bool
deployedTokens(uint256) → address, string, string, address, uint256, string, uint256, uint256
getAllTokensByCreator(address) → address[]
getCreatorFeeStats(address) → uint256, uint256, uint256
getDeploysByPage(uint256, uint256) → tuple[]
getLiquidityConfig(uint256) → tuple
getPlatformFeeStats() → uint256, uint256, uint256
getTokenBytecode(string, string, address) → bytes
holeHook() → address
liquidityConfigCount() → uint256
liquidityConfigs(uint256) → uint160, int24, int24, uint256, uint256, uint256
owner() → address
platformFees() → uint256
platformFeesClaimed() → uint256
tokenCount() → uint256
tokenFeesGenerated(address) → uint256
tokenHook(address) → address
tokenInfoByAddress(address) → address, string, string, address, uint256, string, uint256, uint256
Events (6)
CreatorFeesCollectedERC20TokenCreatedFeesDepositedHoleHookUpdatedOwnershipTransferredTokenPurchased
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_holeHook",
"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"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "CreatorFeesCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "tokenAddress",
"type": "address"
}
],
"name": "ERC20TokenCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "creatorShare",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "platformShare",
"type": "uint256"
}
],
"name": "FeesDeposited",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldHook",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newHook",
"type": "address"
}
],
"name": "HoleHookUpdated",
"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": false,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethSpent",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensReceived",
"type": "uint256"
}
],
"name": "TokenPurchased",
"type": "event"
},
{
"inputs": [],
"name": "CREATOR_SHARE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POOL_MANAGER",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POSITION_MANAGER",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "UNIVERSAL_ROUTER",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "collectFees",
"outputs": [
{
"internalType": "uint256",
"name": "ethCollected",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint160",
"name": "_sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "_tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "_tickUpper",
"type": "int24"
},
{
"internalType": "uint256",
"name": "_amount0Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_amount1Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_virtualAmount",
"type": "uint256"
}
],
"name": "createLiquidityConfig",
"outputs": [
{
"internalType": "uint256",
"name": "configId",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "creatorFeesAccrued",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "creatorFeesClaimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "creatorTokens",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_configId",
"type": "uint256"
}
],
"name": "deleteLiquidityConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_symbol",
"type": "string"
},
{
"internalType": "string",
"name": "_metadata",
"type": "string"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "configId",
"type": "uint256"
}
],
"name": "deployCoin",
"outputs": [
{
"internalType": "uint256",
"name": "tokensReceived",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "deployCoinEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "deployedTokens",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "uint256",
"name": "time",
"type": "uint256"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "uint256",
"name": "marketCapInETH",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalFeesGenerated",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
}
],
"name": "depositFees",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_creator",
"type": "address"
}
],
"name": "getAllTokensByCreator",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "getCreatorFeeStats",
"outputs": [
{
"internalType": "uint256",
"name": "earned",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimed",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimable",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "page",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "order",
"type": "uint256"
}
],
"name": "getDeploysByPage",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "uint256",
"name": "time",
"type": "uint256"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "uint256",
"name": "marketCapInETH",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalFeesGenerated",
"type": "uint256"
}
],
"internalType": "struct Factory.TokenInfo[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_configId",
"type": "uint256"
}
],
"name": "getLiquidityConfig",
"outputs": [
{
"components": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint256",
"name": "amount0Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualAmount",
"type": "uint256"
}
],
"internalType": "struct Factory.LiquidityConfig",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getPlatformFeeStats",
"outputs": [
{
"internalType": "uint256",
"name": "earned",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimed",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimable",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_symbol",
"type": "string"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "getTokenBytecode",
"outputs": [
{
"internalType": "bytes",
"name": "bytecode",
"type": "bytes"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "holeHook",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liquidityConfigCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "liquidityConfigs",
"outputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint256",
"name": "amount0Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualAmount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "platformFees",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "platformFeesClaimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_newHook",
"type": "address"
}
],
"name": "setHoleHook",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_itemsPerPage",
"type": "uint256"
}
],
"name": "setItemsPerPage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "toggleDeployCoin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "tokenCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "tokenFeesGenerated",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "tokenHook",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "tokenInfoByAddress",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "uint256",
"name": "time",
"type": "uint256"
},
{
"internalType": "string",
"name": "metadata",
"type": "string"
},
{
"internalType": "uint256",
"name": "marketCapInETH",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalFeesGenerated",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_configId",
"type": "uint256"
},
{
"internalType": "uint160",
"name": "_sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "_tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "_tickUpper",
"type": "int24"
},
{
"internalType": "uint256",
"name": "_amount0Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_amount1Desired",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_virtualAmount",
"type": "uint256"
}
],
"name": "updateLiquidityConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdrawFeesETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
//
// ██╗ ██╗ ██████╗ ██╗ ███████╗
// ██║ ██║██╔═══██╗██║ ██╔════╝
// ███████║██║ ██║██║ █████╗
// ██╔══██║██║ ██║██║ ██╔══╝
// ██║ ██║╚██████╔╝███████╗███████╗
// ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝
//
// Website: https://hole.finance
// Twitter: https://x.com/HoleFinance_
// Telegram: https://t.me/holefinance
//
pragma solidity ^0.8.20;
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Token} from "./token.sol";
import {Actions} from "v4-periphery/src/libraries/Actions.sol";
import {LiquidityAmounts} from "v4-core/test/utils/LiquidityAmounts.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
contract Factory is ReentrancyGuard, Ownable {
event ERC20TokenCreated(address tokenAddress);
struct TokenInfo {
address tokenAddress;
string name;
string symbol;
address deployer;
uint256 time;
string metadata;
uint256 marketCapInETH;
uint256 totalFeesGenerated;
}
mapping(uint256 => TokenInfo) public deployedTokens;
mapping(address => TokenInfo) public tokenInfoByAddress;
uint256 public tokenCount = 0;
address public holeHook; // Universal hook for all tokens (can be updated)
uint256 private itemsPerPage = 250; // Configurable items per page for pagination
// Mapping to store token addresses by creator/deployer
mapping(address => address[]) public creatorTokens;
// Mapping to track total fees generated per token
mapping(address => uint256) public tokenFeesGenerated;
// Creator's share of swap fees, accrued per creator across all their
// tokens (withdrawn in one go via collectFees)
mapping(address => uint256) public creatorFeesAccrued;
// Lifetime claimed amounts. Together with the accrued/pending values these
// give full stats without extra writes on the hot per-swap deposit path:
// lifetime earned = accrued (claimable) + claimed
mapping(address => uint256) public creatorFeesClaimed;
uint256 public platformFeesClaimed;
// Platform's accumulated share of swap fees (withdrawn via withdrawFeesETH)
uint256 public platformFees;
uint256 public constant CREATOR_SHARE_BPS = 7000; // creator gets 70% of each fee, platform the rest
// Mapping to store the hook address used at deploy time per token
mapping(address => address) public tokenHook;
IAllowanceTransfer constant PERMIT2 = IAllowanceTransfer(address(0x000000000022D473030F116dDEE9F6B43aC78BA3));
// Uniswap v4 on Robinhood Chain (4663)
address public constant POSITION_MANAGER = 0x58daec3116aae6D93017bAAea7749052E8a04fA7;
IPositionManager positionManager = IPositionManager(POSITION_MANAGER);
address public constant POOL_MANAGER = 0x8366a39CC670B4001A1121B8F6A443A643e40951;
IPoolManager poolManager = IPoolManager(POOL_MANAGER);
address public constant UNIVERSAL_ROUTER = 0x8876789976dEcBfCbBbe364623C63652db8C0904;
IUniversalRouter router = IUniversalRouter(UNIVERSAL_ROUTER);
// launches start CLOSED: only the owner can deploy until the platform
// flips this on via toggleDeployCoin()
bool public deployCoinEnabled = false;
// Liquidity configuration struct
struct LiquidityConfig {
uint160 sqrtPriceX96; // sqrtPriceX96 for the pool (address(0) is always currency0)
int24 tickLower; // Lower tick for the position
int24 tickUpper; // Upper tick for the position
uint256 amount0Desired; // Desired amount of currency0 (ETH)
uint256 amount1Desired; // Desired amount of currency1 (Token)
uint256 virtualAmount; // Virtual ETH amount for market cap calculation
}
// Multiple liquidity configurations
mapping(uint256 => LiquidityConfig) public liquidityConfigs;
uint256 public liquidityConfigCount = 0;
event TokenPurchased(address buyer, address tokenOut, uint256 ethSpent, uint256 tokensReceived);
event HoleHookUpdated(address oldHook, address newHook);
event FeesDeposited(address indexed token, address indexed creator, uint256 creatorShare, uint256 platformShare);
event CreatorFeesCollected(address indexed creator, uint256 amount);
constructor(address _holeHook) Ownable(msg.sender) {
holeHook = _holeHook;
// Initialize default liquidity configuration (ID: 0)
// Pool opens as if seeded with 5 ETH of virtual liquidity against the
// full 1B supply: P0 = 1e27/5e18 = 2e8 tokens/ETH, so
// sqrtPriceX96 = sqrt(2e8) * 2^96 and the init tick is ~191148.
// tickUpper is that tick floored to the 200 spacing; tickLower is the
// min usable tick so buy-side liquidity never runs out.
liquidityConfigs[0] = LiquidityConfig({
sqrtPriceX96: 1120455419495722798374638764549163,
tickLower: -887200,
tickUpper: 191000,
amount0Desired: 0, // 0 ETH (single-sided liquidity)
amount1Desired: 1000000000000000000000000000, // 1 billion tokens
virtualAmount: 5000000000000000000 // 5 ETH
});
liquidityConfigCount = 1;
}
function setHoleHook(address _newHook) external onlyOwner {
address oldHook = holeHook;
holeHook = _newHook;
emit HoleHookUpdated(oldHook, _newHook);
}
receive() external payable {}
function deployCoin(string memory _name, string memory _symbol, string memory _metadata, bytes32 salt, uint256 configId) public payable returns (uint256 tokensReceived) {
require(
deployCoinEnabled || msg.sender == owner(),
"Token deployment is currently disabled"
);
require(configId < liquidityConfigCount, "Invalid liquidity config ID");
Token t = new Token{salt: salt}(
_name,
_symbol,
msg.sender,
address(this)
);
address coin_address = address(t);
emit ERC20TokenCreated(coin_address);
// Bookkeeping MUST happen before liquidity + first buy: the hook's
// depositFees callback during the first swap authenticates against
// tokenHook[token] and resolves the creator from tokenInfoByAddress.
uint256 currentTokenCount = tokenCount;
TokenInfo memory newTokenInfo = TokenInfo({
tokenAddress: coin_address,
name: _name,
symbol: _symbol,
deployer: msg.sender,
time: block.timestamp,
metadata: _metadata,
marketCapInETH: 0,
totalFeesGenerated: 0
});
deployedTokens[currentTokenCount] = newTokenInfo;
tokenInfoByAddress[coin_address] = newTokenInfo;
tokenHook[coin_address] = holeHook;
// Add token to creator's array
creatorTokens[msg.sender].push(coin_address);
// Assembly optimized increment (unchecked)
assembly {
sstore(tokenCount.slot, add(currentTokenCount, 1))
}
provideLiquidityV4(coin_address, configId);
tokensReceived = 0; // Initialize return value
if (msg.value > 0) {
uint256 tokensBefore = IERC20(coin_address).balanceOf(address(this));
_buyToken(coin_address, msg.value);
uint256 tokensAfter = IERC20(coin_address).balanceOf(address(this));
tokensReceived = tokensAfter - tokensBefore;
// Transfer tokens to buyer
IERC20(coin_address).transfer(msg.sender, tokensReceived);
emit TokenPurchased(msg.sender, coin_address, msg.value, tokensReceived);
}
return tokensReceived;
}
// Returns the exact init_code that deployCoin will use, so frontends can
// compute the CREATE2 address for a given salt before submitting the tx.
function getTokenBytecode(
string memory _name,
string memory _symbol,
address creator
) public view returns (bytes memory bytecode) {
bytecode = abi.encodePacked(
type(Token).creationCode,
abi.encode(_name, _symbol, creator, address(this))
);
}
function toggleDeployCoin() external onlyOwner {
deployCoinEnabled = !deployCoinEnabled;
}
// Create new liquidity configuration
function createLiquidityConfig(
uint160 _sqrtPriceX96,
int24 _tickLower,
int24 _tickUpper,
uint256 _amount0Desired,
uint256 _amount1Desired,
uint256 _virtualAmount
) external onlyOwner returns (uint256 configId) {
configId = liquidityConfigCount;
liquidityConfigs[configId] = LiquidityConfig({
sqrtPriceX96: _sqrtPriceX96,
tickLower: _tickLower,
tickUpper: _tickUpper,
amount0Desired: _amount0Desired,
amount1Desired: _amount1Desired,
virtualAmount: _virtualAmount
});
liquidityConfigCount++;
return configId;
}
// Update existing liquidity configuration
function updateLiquidityConfig(
uint256 _configId,
uint160 _sqrtPriceX96,
int24 _tickLower,
int24 _tickUpper,
uint256 _amount0Desired,
uint256 _amount1Desired,
uint256 _virtualAmount
) external onlyOwner {
require(_configId < liquidityConfigCount, "Invalid config ID");
liquidityConfigs[_configId] = LiquidityConfig({
sqrtPriceX96: _sqrtPriceX96,
tickLower: _tickLower,
tickUpper: _tickUpper,
amount0Desired: _amount0Desired,
amount1Desired: _amount1Desired,
virtualAmount: _virtualAmount
});
}
// Delete liquidity configuration (sets to zero values)
function deleteLiquidityConfig(uint256 _configId) external onlyOwner {
require(_configId < liquidityConfigCount, "Invalid config ID");
require(_configId != 0, "Cannot delete default config");
delete liquidityConfigs[_configId];
}
// Get liquidity configuration
function getLiquidityConfig(uint256 _configId) external view returns (LiquidityConfig memory) {
require(_configId < liquidityConfigCount, "Invalid config ID");
return liquidityConfigs[_configId];
}
// Change the number of items per page for pagination
function setItemsPerPage(uint256 _itemsPerPage) external onlyOwner {
require(_itemsPerPage > 0 && _itemsPerPage <= 1000, "Items per page must be between 1 and 1000");
itemsPerPage = _itemsPerPage;
}
// Get all tokens for a creator in one call
function getAllTokensByCreator(address _creator) public view returns (address[] memory) {
return creatorTokens[_creator];
}
function getDeploysByPage(uint256 page, uint256 order) public view returns (TokenInfo[] memory) {
require(tokenCount > 0, "No tokens deployed");
uint256 totalPages = (tokenCount + itemsPerPage - 1) / itemsPerPage;
require(page < totalPages, "Page out of range");
uint256 start;
uint256 end;
uint256 j = 0;
if (order == 0) {
// Newest first
start = tokenCount > (page + 1) * itemsPerPage ? tokenCount - (page + 1) * itemsPerPage : 0;
end = tokenCount - page * itemsPerPage;
if (end > tokenCount) end = tokenCount;
} else {
// Oldest first
start = page * itemsPerPage;
end = start + itemsPerPage;
if (end > tokenCount) end = tokenCount;
}
TokenInfo[] memory tokens = new TokenInfo[](end - start);
for (uint256 i = start; i < end; i++) {
uint256 index = order == 0 ? end - 1 - (i - start) : i;
TokenInfo memory info = deployedTokens[index];
info.totalFeesGenerated = tokenFeesGenerated[info.tokenAddress];
tokens[j++] = info;
}
return tokens;
}
// The hook forwards the full swap fee here, attributed to the token.
// The split happens on deposit: 70% accrues to the token's creator,
// 30% to the platform.
function depositFees(address tokenAddress) external payable {
require(msg.sender == tokenHook[tokenAddress], "Only token hook");
address creator = tokenInfoByAddress[tokenAddress].deployer;
uint256 creatorShare = (msg.value * CREATOR_SHARE_BPS) / 10000;
uint256 platformShare = msg.value - creatorShare;
creatorFeesAccrued[creator] += creatorShare;
platformFees += platformShare;
tokenFeesGenerated[tokenAddress] += msg.value;
emit FeesDeposited(tokenAddress, creator, creatorShare, platformShare);
}
function withdrawFeesETH() external onlyOwner {
uint256 amount = platformFees;
require(amount > 0, "No fees to withdraw");
platformFees = 0;
platformFeesClaimed += amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "ETH transfer failed");
}
function provideLiquidityV4(address tokenA, uint256 configId) internal {
LiquidityConfig memory config = liquidityConfigs[configId];
// Since address(0) is always < any token address, it's always currency0
// No need for complex comparison logic
IERC20 token = IERC20(tokenA);
token.approve(address(PERMIT2), type(uint256).max);
PERMIT2.approve(tokenA, address(POSITION_MANAGER), type(uint160).max, type(uint48).max);
PERMIT2.approve(tokenA, address(POOL_MANAGER), type(uint160).max, type(uint48).max);
PoolKey memory pool = PoolKey({
currency0: Currency.wrap(address(0)), // ETH is always currency0
currency1: Currency.wrap(address(tokenA)), // Token is always currency1
fee: 0,
tickSpacing: 200,
hooks: IHooks(holeHook) // Use universal hook
});
poolManager.initialize(pool, config.sqrtPriceX96);
uint128 liquidity = _calculateLiquidity(
config.sqrtPriceX96,
config.tickLower,
config.tickUpper,
config.amount0Desired,
config.amount1Desired
);
bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
bytes memory hookData = new bytes(0);
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(
pool,
config.tickLower,
config.tickUpper,
liquidity,
config.amount0Desired,
config.amount1Desired,
address(0x000000000000000000000000000000000000dEaD), // LP burned — no removal functions exist in this factory anyway; burning makes the lock verifiable on-chain
hookData
);
params[1] = abi.encode(pool.currency0, pool.currency1);
try positionManager.modifyLiquidities(
abi.encode(actions, params),
block.timestamp + 120
) {
// Successfully added liquidity
} catch (bytes memory reason) {
assembly {
revert(add(reason, 0x20), mload(reason))
}
}
}
function _calculateLiquidity(
uint160 sqrtPriceX96,
int24 tickLower,
int24 tickUpper,
uint256 amount0Desired,
uint256 amount1Desired
) private pure returns (uint128) {
return LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
amount0Desired,
amount1Desired
);
}
function _buyToken(address tokenAddress, uint256 ethAmount) internal {
// Create PoolKey for ETH -> Token swap
PoolKey memory pool = PoolKey({
currency0: Currency.wrap(address(0)), // ETH
currency1: Currency.wrap(tokenAddress), // New token
fee: 0,
tickSpacing: 200,
hooks: IHooks(holeHook) // Use universal hook
});
// Encode the Universal Router command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
bytes[] memory inputs = new bytes[](1);
// Encode V4Router actions
bytes memory actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN_SINGLE),
uint8(Actions.SETTLE_ALL),
uint8(Actions.TAKE_ALL)
);
// Prepare parameters for each action
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
pool, // PoolKey
true, // zeroForOne: ETH (currency0) -> Token (currency1)
uint128(ethAmount), // amountIn
uint128(0), // amountOutMinimum: 0 = no minimum requirement
uint256(0), // minHopPriceX36: Robinhood's modified v4-periphery adds this; 0 disables
bytes("") // hookData
);
params[1] = abi.encode(pool.currency0, ethAmount); // SETTLE_ALL params
params[2] = abi.encode(pool.currency1, uint128(0)); // TAKE_ALL params
// Combine actions and params into inputs
inputs[0] = abi.encode(actions, params);
// Execute the swap
uint256 deadline = block.timestamp + 120;
router.execute{value: ethAmount}(commands, inputs, deadline);
// Tokens remain in contract - caller handles transfer and event emission
}
// Pays out the caller's accrued share across all tokens they created.
function collectFees() external nonReentrant returns (uint256 ethCollected) {
ethCollected = creatorFeesAccrued[msg.sender];
require(ethCollected > 0, "No fees to collect");
creatorFeesAccrued[msg.sender] = 0;
creatorFeesClaimed[msg.sender] += ethCollected;
(bool success, ) = payable(msg.sender).call{value: ethCollected}("");
require(success, "ETH transfer failed");
emit CreatorFeesCollected(msg.sender, ethCollected);
return ethCollected;
}
// Lifetime fee stats for a creator across all their tokens.
function getCreatorFeeStats(address creator)
external
view
returns (uint256 earned, uint256 claimed, uint256 claimable)
{
claimable = creatorFeesAccrued[creator];
claimed = creatorFeesClaimed[creator];
earned = claimable + claimed;
}
// Lifetime fee stats for the platform.
function getPlatformFeeStats()
external
view
returns (uint256 earned, uint256 claimed, uint256 claimable)
{
claimable = platformFees;
claimed = platformFeesClaimed;
earned = claimable + claimed;
}
}
Chain explorer3877msChain node106ms