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