Token

0x69aecffcc7269d5163aacabe93168406d60dcae9

Verification
Verified
v0.8.35+commit.47b9dedd
Type
Contract
3,797 bytes
ABI entries
28
10 read · 7 write
License
none

Contract information

Address
0x69aecffcc7269d5163aacabe93168406d60dcae9
Chain
Robinhood Chain (4663)
Compiler
v0.8.35+commit.47b9dedd
Optimization
Enabled
Creation tx
0xae3704551e…159b04217b

Token

Name
Feeless Coin
Symbol
FEELESS
Decimals
18
Holders
108

Read contract (10)

allowance(address, address)uint256
balanceOf(address)uint256
creator()address
decimals()uint8
isLaunchPeriodActive()bool
name()string
owner()address
platform()address
symbol()string
totalSupply()uint256

Events (3)

ApprovalFeesReceivedTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "_name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "_symbol",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "_creator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_platform",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "_launchPeriod",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "allowance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientAllowance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "ERC20InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "approver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidApprover",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "ERC20InvalidSpender",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "FeesReceived",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burn",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "burnFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newCreator",
        "type": "address"
      }
    ],
    "name": "changeCreator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "isLaunchPeriodActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "platform",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

pragma solidity ^0.8.20;
/*

 __  _  _      ____  __  _      _____  ____  ____    ____  ____     __    ___
|  |/ ]| |    |    ||  |/ ]    |     ||    ||    \  /    ||    \   /  ]  /  _]
|  ' / | |     |  | |  ' /     |   __| |  | |  _  ||  o  ||  _  | /  /  /  [_
|    \ | |___  |  | |    \     |  |_   |  | |  |  ||     ||  |  |/  /  |    _]
|     \|     | |  | |     \ __ |   _]  |  | |  |  ||  _  ||  |  /   \_ |   [_
|  .  ||     | |  | |  .  ||  ||  |    |  | |  |  ||  |  ||  |  \     ||     |
|__|\_||_____||____||__|\_||__||__|   |____||__|__||__|__||__|__|\____||_____|
V4 Edition

https://klik.finance
https://x.com/klik_evm

*/

pragma solidity >=0.8.9;

import {SwapParams} from "v4-core/src/types/PoolOperation.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.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 {Actions} from "v4-periphery/src/libraries/Actions.sol";
import {LiquidityAmounts} from "v4-core/test/utils/LiquidityAmounts.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";

interface IStateView {
    function getSlot0(PoolId poolId) external view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee);
}

import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol";
import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";

import "v4-core/test/utils/LiquidityAmounts.sol";

interface IToken {
    function creator() external view returns (address);
    function withdrawFees() external returns (uint256);
}

interface IWETH {
    function withdraw(uint256 amount) external;
}

contract Factory is ReentrancyGuard {
    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 platformController;
    address public klikHook; // 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;

    // Mapping to store the hook address used at deploy time per token
    mapping(address => address) public tokenHook;

    // Uniswap v4 deployment addresses — Robinhood Chain (chain ID 4663)
    IAllowanceTransfer constant PERMIT2 = IAllowanceTransfer(address(0x000000000022D473030F116dDEE9F6B43aC78BA3));
    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 STATE_VIEW = 0xF3334192D15450CdD385c8B70e03f9A6bD9E673b;
    IStateView stateView = IStateView(STATE_VIEW);
    uint256 constant Q96 = 2 ** 96;
    address public constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73; // Canonical WETH on Robinhood Chain
    address public constant UNIVERSAL_ROUTER = 0x8876789976dEcBfCbBbe364623C63652db8C0904;
    IUniversalRouter router = IUniversalRouter(UNIVERSAL_ROUTER);

    uint24 private constant FEE_TIER = 0;

    bool public deployCoinEnabled = true;
    
    // 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
        uint256 penaltyMultiplier;  // Penalty multiplier (50 = 50%, 100 = 100%, 200 = 200%)
    }
    
    // Multiple liquidity configurations
    mapping(uint256 => LiquidityConfig) public liquidityConfigs;
    uint256 public liquidityConfigCount = 0;
    uint256 public launchPeriod = 300; // blocks; set to 0 to disable max-wallet restrictions

    event TokenPurchased(address buyer, address tokenOut, uint256 ethSpent, uint256 tokensReceived);
    event KlikHookUpdated(address oldHook, address newHook);

    constructor(address _klikHook) {
        platformController = msg.sender;
        klikHook = _klikHook;

        // Pre-seeded liquidity configurations (IDs 0-4). All single-sided:
        // full 1B token supply, no ETH. virtualAmount = starting market cap
        // in ETH; sqrtPriceX96 prices the full supply at exactly that amount.
        // penaltyMultiplier 0 = no deploy-buy tax (100 = standard penalty).

        // ID 0: 0.69 ETH virtual liquidity (default)
        liquidityConfigs[0] = LiquidityConfig({
            sqrtPriceX96: 3014831488601586337191090825970934,
            tickLower: -887200,
            tickUpper: 210800,
            amount0Desired: 0,
            amount1Desired: 1000000000000000000000000000,
            virtualAmount: 690000000000000000,
            penaltyMultiplier: 0
        });

        // ID 1: 1 ETH virtual liquidity
        liquidityConfigs[1] = LiquidityConfig({
            sqrtPriceX96: 2505411999795360582221170761428213,
            tickLower: -887200,
            tickUpper: 207200,
            amount0Desired: 0,
            amount1Desired: 1000000000000000000000000000,
            virtualAmount: 1000000000000000000,
            penaltyMultiplier: 0
        });

        // ID 2: 2 ETH virtual liquidity
        liquidityConfigs[2] = LiquidityConfig({
            sqrtPriceX96: 1771577727172025373304338615273325,
            tickLower: -887200,
            tickUpper: 200200,
            amount0Desired: 0,
            amount1Desired: 1000000000000000000000000000,
            virtualAmount: 2000000000000000000,
            penaltyMultiplier: 0
        });

        // ID 3: 5 ETH virtual liquidity
        liquidityConfigs[3] = LiquidityConfig({
            sqrtPriceX96: 1120408587790087695236213992013890,
            tickLower: -887200,
            tickUpper: 191000,
            amount0Desired: 0,
            amount1Desired: 1000000000000000000000000000,
            virtualAmount: 5000000000000000000,
            penaltyMultiplier: 0
        });

        // ID 4: 10 ETH virtual liquidity
        liquidityConfigs[4] = LiquidityConfig({
            sqrtPriceX96: 792280926924313289846529216293289,
            tickLower: -887200,
            tickUpper: 184200,
            amount0Desired: 0,
            amount1Desired: 1000000000000000000000000000,
            virtualAmount: 10000000000000000000,
            penaltyMultiplier: 0
        });

        liquidityConfigCount = 5;
    }

    function setKlikHook(address _newHook) external {
        require(msg.sender == platformController, "Caller is not controller");
        address oldHook = klikHook;
        klikHook = _newHook;
        emit KlikHookUpdated(oldHook, _newHook);
    }

    function setLaunchPeriod(uint256 _blocks) external {
        require(msg.sender == platformController, "Caller is not controller");
        launchPeriod = _blocks;
    }

    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, "Token deployment is currently disabled");
        require(configId < liquidityConfigCount, "Invalid liquidity config ID");
        
        Token t = new Token{salt: salt}(
            _name,
            _symbol,
            msg.sender,
            address(this),
            launchPeriod
        );

        address coin_address = address(t);

        emit ERC20TokenCreated(coin_address);

        provideLiquidityV4(coin_address, configId);

        tokensReceived = 0; // Initialize return value

        if (msg.value > 0) {
            LiquidityConfig memory config = liquidityConfigs[configId];
            uint256 basePenalty = getPenalty(msg.value); // in basis points (e.g., 2500 = 25%)
            uint256 taxBps = (basePenalty * config.penaltyMultiplier) / 100; // Apply config multiplier
            uint256 tax;
            uint256 amountAfterTax;

            // Assembly optimized math operations
            unchecked {
                assembly {
                    tax := div(mul(callvalue(), taxBps), 10000)
                    amountAfterTax := sub(callvalue(), tax)
                }
            }

            uint256 tokensBefore = IERC20(coin_address).balanceOf(address(this));
            _buyToken(coin_address, amountAfterTax);
            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, amountAfterTax, tokensReceived);
        }

        // Cache tokenCount to avoid multiple SLOAD operations
        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] = klikHook;

        // Add token to creator's array
        creatorTokens[msg.sender].push(coin_address);

        // Assembly optimized increment (unchecked)
        assembly {
            sstore(tokenCount.slot, add(currentTokenCount, 1))
        }

        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.
    // launchPeriod is read from current state — callers must re-read this if
    // the controller changes launchPeriod between salt-mining and deploy, or
    // the predicted address will diverge from the actual one.
    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), launchPeriod)
        );
    }

    function getPenalty(uint256 ethAmount) public pure returns (uint256) {
        if (ethAmount < 0.05 ether) return 0;
        if (ethAmount >= 0.30 ether) return 5000; // max 50%

        uint256 slope = 18000;
        uint256 delta = ethAmount - 0.05 ether;
        uint256 penalty = 500 + (delta * slope) / 1 ether;

        return penalty;
    }

    function toggleDeployCoin() external {
        require(msg.sender == platformController, "Caller is not controller");
        deployCoinEnabled = !deployCoinEnabled;
    }

    // Create new liquidity configuration
    function createLiquidityConfig(
        uint160 _sqrtPriceX96,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _amount0Desired,
        uint256 _amount1Desired,
        uint256 _virtualAmount,
        uint256 _penaltyMultiplier
    ) external returns (uint256 configId) {
        require(msg.sender == platformController, "Only platform controller can create liquidity config");
        require(_penaltyMultiplier <= 500, "Penalty multiplier must be at most 500%");

        configId = liquidityConfigCount;
        liquidityConfigs[configId] = LiquidityConfig({
            sqrtPriceX96: _sqrtPriceX96,
            tickLower: _tickLower,
            tickUpper: _tickUpper,
            amount0Desired: _amount0Desired,
            amount1Desired: _amount1Desired,
            virtualAmount: _virtualAmount,
            penaltyMultiplier: _penaltyMultiplier
        });
        liquidityConfigCount++;

        return configId;
    }
    
    // Update existing liquidity configuration
    function updateLiquidityConfig(
        uint256 _configId,
        uint160 _sqrtPriceX96,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 _amount0Desired,
        uint256 _amount1Desired,
        uint256 _virtualAmount,
        uint256 _penaltyMultiplier
    ) external {
        require(msg.sender == platformController, "Only platform controller can update liquidity config");
        require(_configId < liquidityConfigCount, "Invalid config ID");
        require(_penaltyMultiplier <= 500, "Penalty multiplier must be at most 500%");

        liquidityConfigs[_configId] = LiquidityConfig({
            sqrtPriceX96: _sqrtPriceX96,
            tickLower: _tickLower,
            tickUpper: _tickUpper,
            amount0Desired: _amount0Desired,
            amount1Desired: _amount1Desired,
            virtualAmount: _virtualAmount,
            penaltyMultiplier: _penaltyMultiplier
        });
    }
    
    // Delete liquidity configuration (sets to zero values)
    function deleteLiquidityConfig(uint256 _configId) external {
        require(msg.sender == platformController, "Only platform controller can delete liquidity config");
        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 {
        require(msg.sender == platformController, "Only platform controller can change items per page");
        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];

            // Calculate market cap using the correct formula
            uint256 marketCap = getMarketCap(info.tokenAddress);

            tokens[j++] = TokenInfo({
                tokenAddress: info.tokenAddress,
                name: info.name,
                symbol: info.symbol,
                deployer: info.deployer,
                time: info.time,
                metadata: info.metadata,
                marketCapInETH: marketCap,
                totalFeesGenerated: tokenFeesGenerated[info.tokenAddress]
            });
        }

        return tokens;
    }

    function withdrawFeesWETH() external {
        require(msg.sender == platformController, "Caller is not controller");
        uint256 wethBalance = IERC20(WETH).balanceOf(address(this));
        require(wethBalance > 0, "No WETH to withdraw");

        IWETH(WETH).withdraw(wethBalance);

        (bool success, ) = msg.sender.call{value: wethBalance}("");
        require(success, "ETH transfer failed");
    }

    function withdrawFeesETH() external {
        require(msg.sender == platformController, "Caller is not controller");

        uint256 ethBalance = address(this).balance;
        require(ethBalance > 0, "No ETH to withdraw");

        (bool success, ) = msg.sender.call{value: ethBalance}("");
        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(klikHook) // 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
        );
    }

    // Mirror of IV4Router.ExactInputSingleParams, declared locally so its
    // poolKey field uses THIS file's PoolKey type. Referencing IV4Router's
    // struct directly fails to compile: its PoolKey comes from a different
    // import path (@uniswap/v4-core) than the factory's (v4-core), and Solidity
    // treats the two as distinct types. The ABI layout is identical, which is
    // all the Universal Router's CalldataDecoder reads.
    struct ExactInputSingleParams {
        PoolKey poolKey;
        bool zeroForOne;
        uint128 amountIn;
        uint128 amountOutMinimum;
        bytes hookData;
    }

    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(klikHook) // 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.
        // params[0] MUST be the ExactInputSingleParams struct (not its fields
        // spread out) — the struct encoding carries a leading offset word that
        // the Universal Router's CalldataDecoder requires. Spreading the fields
        // omits that word and reverts SliceOutOfBounds() on current UR builds.
        bytes[] memory params = new bytes[](3);
        params[0] = abi.encode(
            ExactInputSingleParams({
                poolKey: pool,
                zeroForOne: true, // ETH (currency0) -> Token (currency1)
                amountIn: uint128(ethAmount),
                amountOutMinimum: uint128(0), // 0 = no minimum requirement
                hookData: bytes("")
            })
        );
        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
    }

    function collectFees(address tokenAddress) external nonReentrant returns (uint256 ethCollected) {
        address creator = IToken(tokenAddress).creator();
        require(msg.sender == creator || msg.sender == platformController, "Not authorized");

        uint256 balanceBefore = address(this).balance;
        IToken(tokenAddress).withdrawFees();
        uint256 balanceAfter = address(this).balance;
        
        ethCollected = balanceAfter - balanceBefore;

        if (ethCollected > 0) {
            tokenFeesGenerated[tokenAddress] += ethCollected;

            // Platform share is taken atomically by the hook at swap time.
            // Everything in the token contract is the creator's portion.
            (bool success, ) = payable(creator).call{value: ethCollected}("");
            require(success, "ETH transfer to creator failed");
        }

        return ethCollected;
    }

    function changeTokenFeeReceiver(address tokenAddress, address newCreator) external {
        address currentCreator = IToken(tokenAddress).creator();
        require(msg.sender == platformController || msg.sender == currentCreator, "Not authorized");
        require(newCreator != address(0), "New creator cannot be zero address");

        Token(payable(tokenAddress)).changeCreator(newCreator);
    }

    function getTokenPrice(address tokenAddress) public view returns (bytes32 poolIdBytes, uint160 sqrtPrice, uint256 calculatedPrice, uint256 marketCapETH) {
        // Create PoolKey for ETH/Token pool
        address hook = tokenHook[tokenAddress] != address(0) ? tokenHook[tokenAddress] : klikHook;
        PoolKey memory poolKey = PoolKey({
            currency0: Currency.wrap(address(0)), // ETH
            currency1: Currency.wrap(tokenAddress), // Token
            fee: 0,
            tickSpacing: 200,
            hooks: IHooks(hook)
        });

        // V4 approach: Get actual pool state using StateView
        PoolId poolId = PoolId.wrap(keccak256(abi.encode(poolKey)));
        poolIdBytes = PoolId.unwrap(poolId);

        try stateView.getSlot0(poolId) returns (uint160 sqrtPriceX96, int24, uint24, uint24) {
            sqrtPrice = sqrtPriceX96;

            if (sqrtPriceX96 > 0) {
                uint256 sqrtPriceX96_uint = uint256(sqrtPriceX96);
                uint256 q96 = 2**96;

                uint256 scaledDivisor = (q96 * 1e18) / sqrtPriceX96_uint;
                calculatedPrice = (scaledDivisor * q96) / sqrtPriceX96_uint; // finalPrice

                // Market Cap = price * totalSupply
                uint256 totalSupply = IERC20(tokenAddress).totalSupply();
                marketCapETH = calculatedPrice * totalSupply/1e18;

                return (poolIdBytes, sqrtPrice, calculatedPrice, marketCapETH);
            }
        } catch {
            // Pool doesn't exist - return 0
            return (poolIdBytes, 0, 0, 0);
        }

        return (poolIdBytes, 0, 0, 0);
    }

    function getMarketCap(address tokenAddress) public view returns (uint256 marketCapETH) {
        (, , , marketCapETH) = getTokenPrice(tokenAddress);
        return marketCapETH;
    }
    
    // Get total fees generated by a specific token
    function getTokenFeesGenerated(address tokenAddress) public view returns (uint256) {
        return tokenFeesGenerated[tokenAddress];
    }
}

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

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Token is ERC20, ERC20Burnable {
    address public platform;
    address public creator;
    address private _owner;
    uint256 private launchBlock;
    uint256 private maxTxAmount;
    uint256 private immutable LAUNCH_PERIOD;
    uint256 private constant MAX_WALLET_PERCENTAGE = 2; // 2% of total supply

    address immutable pool = 0x8366a39CC670B4001A1121B8F6A443A643e40951; // Robinhood Chain PoolManager

    // Track transfers per tx.origin per block to detect multi-swaps
    mapping(address => uint256) private tokensFromPoolPerOrigin;

    event FeesReceived(uint256 amount);

    constructor(
        string memory _name,
        string memory _symbol,
        address _creator,
        address _platform,
        uint256 _launchPeriod
    ) ERC20(_name, _symbol) {
        platform = _platform;
        creator = _creator;
        _owner = address(0);
        launchBlock = block.number;
        LAUNCH_PERIOD = _launchPeriod;

        uint256 totalTokens = 1000000000 * 10 ** decimals();
        maxTxAmount = (totalTokens * MAX_WALLET_PERCENTAGE) / 100;

        _mint(_platform, totalTokens);
    }

    function _update(address from, address to, uint256 value) internal override {
        if (block.number > launchBlock && block.number <= launchBlock + LAUNCH_PERIOD) {
            // Get pool address for exemption

            if (from == pool && to != platform && to != creator) {
                tokensFromPoolPerOrigin[tx.origin] += value;
                require(
                    tokensFromPoolPerOrigin[tx.origin] <= maxTxAmount*110/100,
                    "Keeping 2% pool Limits In Kontrol"
                );
            }

            if (to != creator && to != platform && to != pool && from != address(0)) {
                require(
                    balanceOf(to) + value <= maxTxAmount,
                    "Max wallet limit exceeded during launch period"
                );
            }
        }

        // Block all buys at launch block except exempted transfers
        if (block.number == launchBlock &&
            from != address(0) &&
            to != platform &&
            from != platform &&
            !(from == platform && to == creator)) { // Only platform can send to creator
            revert("No buys allowed during launch block!");
        }

        super._update(from, to, value);
    }

    function isLaunchPeriodActive() public view returns (bool) {
        return block.number <= launchBlock + LAUNCH_PERIOD;
    }

    // Returns address(0) so on-chain tools (Etherscan, token scanners) report ownership
    // as renounced. _owner is hardcoded to zero in the constructor and is never reassignable.
    function owner() public view returns (address) {
        return _owner;
    }

    // Receive ETH from hook fees
    receive() external payable {
        emit FeesReceived(msg.value);
    }

    // Allow only Factory to withdraw accumulated fees
    function withdrawFees() external returns (uint256 balance) {
        require(msg.sender == platform, "Only factory can withdraw");
        balance = address(this).balance;
        require(balance > 0, "No fees to withdraw");

        (bool success, ) = payable(platform).call{value: balance}("");
        require(success, "Fee withdrawal failed");
    }

    function changeCreator(address newCreator) external {
        require(msg.sender == platform, "Only platform can change creator"); // Updating Fee Receiver
        creator = newCreator;
    }
}
Chain explorer3821msChain node79ms