V4LaunchFactory

0xadcad0b3dbb1ca35ba9d097bd773cf8b6beea95e

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
20,634 bytes
ABI entries
67
31 read · 5 write
License
none

Contract information

Address
0xadcad0b3dbb1ca35ba9d097bd773cf8b6beea95e
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x64786de47d…193464ea7b

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (31)

HOOK_FLAGS()uint160
INITIAL_TOKEN_TICK()int24
MAX_LOCKED_DUST()uint256
MAX_NAME_LENGTH()uint256
MAX_SYMBOL_LENGTH()uint256
MAX_TOKEN_URI_LENGTH()uint256
POOL_LP_FEE()uint24
POOL_TICK_SPACING()int24
TOKEN_SUPPLY()uint256
allTokens(uint256)address
bootstrapAmount()uint256
bootstrapper()address
effectiveSalt(address, bytes32, bytes32)bytes32
feeEscrow()address
hookCreationCodeHash(address, bytes32)bytes32
launchGeometry(address)int24, int24, int24, bool
launches(address)address, address, address, bytes32, bytes32, address, uint256, uint128, uint256, uint256
liquidityLockDeployer()address
owner()address
paused()bool
pendingOwner()address
permit2()address
poolManager()address
positionManager()address
predictHookAddress(bytes32, address, bytes32)address
predictTokenAddress(address, tuple)address
renounceOwnership()
tokenSaltUsed(bytes32)bool
totalTokens()uint256
universalRouter()address
weth()address

Events (5)

OwnershipTransferStartedOwnershipTransferredPausedUnpausedV4TokenLaunched

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "contract V4FeeEscrow",
        "name": "feeEscrow_",
        "type": "address"
      },
      {
        "internalType": "contract IPositionManager",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "contract IAllowanceTransfer",
        "name": "permit2_",
        "type": "address"
      },
      {
        "internalType": "contract V4LiquidityLockDeployer",
        "name": "liquidityLockDeployer_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "universalRouter_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "bootstrapAmount_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "check",
        "type": "string"
      }
    ],
    "name": "DeploymentVerificationFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "configuredFactory",
        "type": "address"
      }
    ],
    "name": "EscrowNotWired",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenDust",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wethDust",
        "type": "uint256"
      }
    ],
    "name": "ExcessLockedDust",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "predicted",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "deployed",
        "type": "address"
      }
    ],
    "name": "HookAddressMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "hook",
        "type": "address"
      }
    ],
    "name": "HookPermissionsMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "InvalidBootstrapAmount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "required",
        "type": "uint256"
      }
    ],
    "name": "InvalidBootstrapValue",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "InvalidContract",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "InvalidNameLength",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "InvalidSymbolLength",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "required",
        "type": "uint256"
      }
    ],
    "name": "InvalidTokenSupply",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "InvalidTokenURI",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LiquidityCalculationFailed",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OwnershipRenounceDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TokenAlreadyExists",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TokenDeploymentFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnsupportedTokenURIScheme",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UppercaseTokenURI",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroCreatorId",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "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": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "creatorId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "launcher",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "liquidityLock",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "bootstrapTokenAmount",
        "type": "uint256"
      }
    ],
    "name": "V4TokenLaunched",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "HOOK_FLAGS",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "INITIAL_TOKEN_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_LOCKED_DUST",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_NAME_LENGTH",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SYMBOL_LENGTH",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TOKEN_URI_LENGTH",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_LP_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "allTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "bootstrapAmount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "bootstrapper",
    "outputs": [
      {
        "internalType": "contract V4PoolBootstrapper",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "launcher",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "creatorId",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "tokenSalt",
        "type": "bytes32"
      }
    ],
    "name": "effectiveSalt",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeEscrow",
    "outputs": [
      {
        "internalType": "contract V4FeeEscrow",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "creatorId",
        "type": "bytes32"
      }
    ],
    "name": "hookCreationCodeHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "tokenURI",
            "type": "string"
          },
          {
            "internalType": "bytes32",
            "name": "creatorId",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "tokenSalt",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "hookSalt",
            "type": "bytes32"
          },
          {
            "internalType": "uint256",
            "name": "tokenSupply",
            "type": "uint256"
          }
        ],
        "internalType": "struct V4LaunchFactory.LaunchParams",
        "name": "params",
        "type": "tuple"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "hook",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "liquidityLock",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "poolId",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "creatorId",
            "type": "bytes32"
          },
          {
            "internalType": "address",
            "name": "launcher",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "tokenSupply",
            "type": "uint256"
          },
          {
            "internalType": "uint128",
            "name": "liquidity",
            "type": "uint128"
          },
          {
            "internalType": "uint256",
            "name": "bootstrapTokenAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "createdAt",
            "type": "uint256"
          }
        ],
        "internalType": "struct V4LaunchFactory.LaunchRecord",
        "name": "record",
        "type": "tuple"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "launchGeometry",
    "outputs": [
      {
        "internalType": "int24",
        "name": "initialTick",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "bool",
        "name": "tokenIsCurrency0",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "launches",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "hook",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "liquidityLock",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "creatorId",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "launcher",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenSupply",
        "type": "uint256"
      },
      {
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "internalType": "uint256",
        "name": "bootstrapTokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "createdAt",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "liquidityLockDeployer",
    "outputs": [
      {
        "internalType": "contract V4LiquidityLockDeployer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "permit2",
    "outputs": [
      {
        "internalType": "contract IAllowanceTransfer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract IPositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "hookSalt",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "creatorId",
        "type": "bytes32"
      }
    ],
    "name": "predictHookAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "launcher",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "tokenURI",
            "type": "string"
          },
          {
            "internalType": "bytes32",
            "name": "creatorId",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "tokenSalt",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "hookSalt",
            "type": "bytes32"
          },
          {
            "internalType": "uint256",
            "name": "tokenSupply",
            "type": "uint256"
          }
        ],
        "internalType": "struct V4LaunchFactory.LaunchParams",
        "name": "params",
        "type": "tuple"
      }
    ],
    "name": "predictTokenAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "effectiveSalt",
        "type": "bytes32"
      }
    ],
    "name": "tokenSaltUsed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "used",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalTokens",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "universalRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IAllowanceTransfer} from "@uniswap/v4-periphery/lib/permit2/src/interfaces/IAllowanceTransfer.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {CreatorFeeHook} from "./CreatorFeeHook.sol";
import {V4FeeEscrow} from "./V4FeeEscrow.sol";
import {V4LaunchToken} from "./V4LaunchToken.sol";
import {V4LiquidityLock} from "./V4LiquidityLock.sol";
import {V4LiquidityLockDeployer} from "./V4LiquidityLockDeployer.sol";
import {V4PoolBootstrapper} from "./V4PoolBootstrapper.sol";
import {IV4FeeEscrow} from "./interfaces/IV4FeeEscrow.sol";

/// @notice Creates a fixed-supply token, its immutable fee hook, and permanently locked one-sided liquidity.
contract V4LaunchFactory is Ownable2Step, Pausable {
    using PoolIdLibrary for PoolKey;
    using SafeERC20 for IERC20;
    using StateLibrary for IPoolManager;

    uint160 public constant HOOK_FLAGS = uint160(
        Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG
            | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG
    );
    uint24 public constant POOL_LP_FEE = 0;
    int24 public constant POOL_TICK_SPACING = 60;
    int24 public constant INITIAL_TOKEN_TICK = -204_180;
    uint256 public constant TOKEN_SUPPLY = 1_000_000_000 ether;
    uint256 public constant MAX_NAME_LENGTH = 64;
    uint256 public constant MAX_SYMBOL_LENGTH = 16;
    uint256 public constant MAX_TOKEN_URI_LENGTH = 512;
    uint256 public constant MAX_LOCKED_DUST = 1 gwei;

    error ZeroAddress();
    error InvalidContract(address account);
    error InvalidBootstrapAmount(uint256 amount);
    error InvalidBootstrapValue(uint256 supplied, uint256 required);
    error ZeroCreatorId();
    error InvalidTokenSupply(uint256 supplied, uint256 required);
    error InvalidNameLength(uint256 length);
    error InvalidSymbolLength(uint256 length);
    error InvalidTokenURI(uint256 length);
    error UnsupportedTokenURIScheme();
    error UppercaseTokenURI();
    error TokenAlreadyExists();
    error TokenDeploymentFailed();
    error HookAddressMismatch(address predicted, address deployed);
    error HookPermissionsMismatch(address hook);
    error EscrowNotWired(address configuredFactory);
    error LiquidityCalculationFailed();
    error ExcessLockedDust(uint256 tokenDust, uint256 wethDust);
    error DeploymentVerificationFailed(string check);
    error OwnershipRenounceDisabled();

    struct LaunchParams {
        string name;
        string symbol;
        string tokenURI;
        bytes32 creatorId;
        bytes32 tokenSalt;
        bytes32 hookSalt;
        uint256 tokenSupply;
    }

    struct LaunchRecord {
        address token;
        address hook;
        address liquidityLock;
        bytes32 poolId;
        bytes32 creatorId;
        address launcher;
        uint256 tokenSupply;
        uint128 liquidity;
        uint256 bootstrapTokenAmount;
        uint256 createdAt;
    }

    struct SeedResult {
        V4LiquidityLock lock;
        uint128 liquidity;
        uint160 sqrtPriceX96;
        int24 initialTick;
        int24 tickLower;
        int24 tickUpper;
    }

    event V4TokenLaunched(
        address indexed token,
        address indexed hook,
        bytes32 indexed creatorId,
        address launcher,
        address liquidityLock,
        bytes32 poolId,
        uint128 liquidity,
        uint256 bootstrapTokenAmount
    );

    IPoolManager public immutable poolManager;
    IERC20 public immutable weth;
    V4FeeEscrow public immutable feeEscrow;
    IPositionManager public immutable positionManager;
    IAllowanceTransfer public immutable permit2;
    V4LiquidityLockDeployer public immutable liquidityLockDeployer;
    address public immutable universalRouter;
    uint256 public immutable bootstrapAmount;
    V4PoolBootstrapper public immutable bootstrapper;
    mapping(bytes32 effectiveSalt => bool used) public tokenSaltUsed;
    mapping(address token => LaunchRecord record) public launches;
    address[] public allTokens;

    constructor(
        IPoolManager poolManager_,
        address weth_,
        V4FeeEscrow feeEscrow_,
        IPositionManager positionManager_,
        IAllowanceTransfer permit2_,
        V4LiquidityLockDeployer liquidityLockDeployer_,
        address universalRouter_,
        uint256 bootstrapAmount_,
        address owner_
    ) Ownable(owner_) {
        if (
            address(poolManager_) == address(0) || weth_ == address(0) || address(feeEscrow_) == address(0)
                || address(positionManager_) == address(0) || address(permit2_) == address(0)
                || address(liquidityLockDeployer_) == address(0) || universalRouter_ == address(0)
        ) {
            revert ZeroAddress();
        }
        if (bootstrapAmount_ == 0 || bootstrapAmount_ > type(uint128).max) {
            revert InvalidBootstrapAmount(bootstrapAmount_);
        }
        if (address(poolManager_).code.length == 0) revert InvalidContract(address(poolManager_));
        if (weth_.code.length == 0) revert InvalidContract(weth_);
        if (address(feeEscrow_).code.length == 0) revert InvalidContract(address(feeEscrow_));
        if (address(positionManager_).code.length == 0) revert InvalidContract(address(positionManager_));
        if (address(permit2_).code.length == 0) revert InvalidContract(address(permit2_));
        if (address(liquidityLockDeployer_).code.length == 0) {
            revert InvalidContract(address(liquidityLockDeployer_));
        }
        if (universalRouter_.code.length == 0) revert InvalidContract(universalRouter_);
        if (address(feeEscrow_.weth()) != weth_) revert DeploymentVerificationFailed("escrow WETH");
        if (address(positionManager_.poolManager()) != address(poolManager_)) {
            revert DeploymentVerificationFailed("position manager");
        }
        poolManager = poolManager_;
        weth = IERC20(weth_);
        feeEscrow = feeEscrow_;
        positionManager = positionManager_;
        permit2 = permit2_;
        liquidityLockDeployer = liquidityLockDeployer_;
        universalRouter = universalRouter_;
        bootstrapAmount = bootstrapAmount_;
        bootstrapper = new V4PoolBootstrapper(address(this), universalRouter_, weth_);
    }

    function launch(LaunchParams calldata params) external payable whenNotPaused returns (LaunchRecord memory record) {
        if (params.creatorId == bytes32(0)) revert ZeroCreatorId();
        if (params.tokenSupply != TOKEN_SUPPLY) revert InvalidTokenSupply(params.tokenSupply, TOKEN_SUPPLY);
        if (msg.value != bootstrapAmount) revert InvalidBootstrapValue(msg.value, bootstrapAmount);
        if (feeEscrow.factory() != address(this)) revert EscrowNotWired(feeEscrow.factory());
        _validateTokenStrings(params.name, params.symbol);
        _validateTokenURI(params.tokenURI);

        bytes32 effectiveTokenSalt = effectiveSalt(msg.sender, params.creatorId, params.tokenSalt);
        if (tokenSaltUsed[effectiveTokenSalt]) revert TokenAlreadyExists();
        tokenSaltUsed[effectiveTokenSalt] = true;

        V4LaunchToken token = _deployToken(params, effectiveTokenSalt);
        CreatorFeeHook hook = _deployHook(params.hookSalt, address(token), params.creatorId);
        PoolKey memory key = _poolKey(address(token), address(hook));

        feeEscrow.registerHook(address(hook), params.creatorId);
        SeedResult memory seeded = _seedPool(token, key, params.tokenSupply);
        PoolId poolId = key.toId();
        record.token = address(token);
        record.hook = address(hook);
        record.liquidityLock = address(seeded.lock);
        record.poolId = PoolId.unwrap(poolId);
        record.creatorId = params.creatorId;
        record.launcher = msg.sender;
        record.tokenSupply = params.tokenSupply;
        record.liquidity = seeded.liquidity;
        record.bootstrapTokenAmount =
            bootstrapper.bootstrap{value: msg.value}(key, address(token), address(seeded.lock), hook);
        if (poolManager.getLiquidity(poolId) != seeded.liquidity) {
            revert DeploymentVerificationFailed("active liquidity");
        }
        uint256 lockedTokenBalance = token.balanceOf(address(seeded.lock));
        if (
            lockedTokenBalance < record.bootstrapTokenAmount
                || lockedTokenBalance - record.bootstrapTokenAmount > MAX_LOCKED_DUST
        ) {
            revert DeploymentVerificationFailed("bootstrap token lock");
        }
        record.createdAt = block.timestamp;
        launches[address(token)] = record;
        allTokens.push(address(token));
        _emitLaunch(record);
    }

    function predictTokenAddress(address launcher, LaunchParams calldata params) external view returns (address) {
        bytes32 salt = effectiveSalt(launcher, params.creatorId, params.tokenSalt);
        bytes memory bytecode = abi.encodePacked(
            type(V4LaunchToken).creationCode,
            abi.encode(params.name, params.symbol, params.tokenURI, params.tokenSupply, address(this))
        );
        return Create2.computeAddress(salt, keccak256(bytecode));
    }

    function predictHookAddress(bytes32 hookSalt, address token, bytes32 creatorId) public view returns (address) {
        bytes memory bytecode = abi.encodePacked(
            type(CreatorFeeHook).creationCode,
            abi.encode(poolManager, address(weth), feeEscrow, address(this), token, creatorId)
        );
        return Create2.computeAddress(hookSalt, keccak256(bytecode));
    }

    function hookCreationCodeHash(address token, bytes32 creatorId) external view returns (bytes32) {
        return keccak256(
            abi.encodePacked(
                type(CreatorFeeHook).creationCode,
                abi.encode(poolManager, address(weth), feeEscrow, address(this), token, creatorId)
            )
        );
    }

    function effectiveSalt(address launcher, bytes32 creatorId, bytes32 tokenSalt) public pure returns (bytes32) {
        return keccak256(abi.encode(launcher, creatorId, tokenSalt));
    }

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

    /// @notice Returns the ordering-aware boundary range used for every token-only launch.
    function launchGeometry(address token)
        public
        view
        returns (int24 initialTick, int24 tickLower, int24 tickUpper, bool tokenIsCurrency0)
    {
        tokenIsCurrency0 = token < address(weth);
        if (tokenIsCurrency0) {
            initialTick = INITIAL_TOKEN_TICK;
            tickLower = INITIAL_TOKEN_TICK;
            tickUpper = TickMath.maxUsableTick(POOL_TICK_SPACING);
        } else {
            initialTick = -INITIAL_TOKEN_TICK;
            tickLower = TickMath.minUsableTick(POOL_TICK_SPACING);
            tickUpper = -INITIAL_TOKEN_TICK;
        }
    }

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    function renounceOwnership() public pure override {
        revert OwnershipRenounceDisabled();
    }

    function _emitLaunch(LaunchRecord memory record) internal {
        emit V4TokenLaunched(
            record.token,
            record.hook,
            record.creatorId,
            record.launcher,
            record.liquidityLock,
            record.poolId,
            record.liquidity,
            record.bootstrapTokenAmount
        );
    }

    function _deployToken(LaunchParams calldata params, bytes32 salt) internal returns (V4LaunchToken token) {
        bytes memory bytecode = abi.encodePacked(
            type(V4LaunchToken).creationCode,
            abi.encode(params.name, params.symbol, params.tokenURI, params.tokenSupply, address(this))
        );
        address deployed;
        assembly ("memory-safe") {
            deployed := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
        }
        if (deployed == address(0)) revert TokenDeploymentFailed();
        token = V4LaunchToken(deployed);
    }

    function _deployHook(bytes32 salt, address token, bytes32 creatorId) internal returns (CreatorFeeHook hook) {
        address predicted = predictHookAddress(salt, token, creatorId);
        hook = new CreatorFeeHook{salt: salt}(
            poolManager, address(weth), IV4FeeEscrow(address(feeEscrow)), address(this), token, creatorId
        );
        if (address(hook) != predicted) revert HookAddressMismatch(predicted, address(hook));
        if (uint160(address(hook)) & Hooks.ALL_HOOK_MASK != HOOK_FLAGS) {
            revert HookPermissionsMismatch(address(hook));
        }
    }

    function _liquidityForLaunch(uint256 tokenSupply, int24 tickLower, int24 tickUpper, bool tokenIsCurrency0)
        internal
        pure
        returns (uint128 liquidity)
    {
        uint160 sqrtLowerX96 = TickMath.getSqrtPriceAtTick(tickLower);
        uint160 sqrtUpperX96 = TickMath.getSqrtPriceAtTick(tickUpper);
        liquidity = tokenIsCurrency0
            ? LiquidityAmounts.getLiquidityForAmount0(sqrtLowerX96, sqrtUpperX96, tokenSupply)
            : LiquidityAmounts.getLiquidityForAmount1(sqrtLowerX96, sqrtUpperX96, tokenSupply);
        if (liquidity == 0) revert LiquidityCalculationFailed();
    }

    function _seedPool(V4LaunchToken token, PoolKey memory key, uint256 tokenSupply)
        internal
        returns (SeedResult memory result)
    {
        bool tokenIsCurrency0;
        (result.initialTick, result.tickLower, result.tickUpper, tokenIsCurrency0) = launchGeometry(address(token));
        result.lock = liquidityLockDeployer.deploy(
            positionManager, permit2, address(this), address(token), key, result.tickLower, result.tickUpper
        );
        IERC20(address(token)).safeTransfer(address(result.lock), tokenSupply);

        result.sqrtPriceX96 = TickMath.getSqrtPriceAtTick(result.initialTick);
        poolManager.initialize(key, result.sqrtPriceX96);
        result.liquidity = _liquidityForLaunch(tokenSupply, result.tickLower, result.tickUpper, tokenIsCurrency0);
        (uint256 amount0, uint256 amount1) = result.lock.seed(result.liquidity);
        uint256 tokenAmount = tokenIsCurrency0 ? amount0 : amount1;
        uint256 wethAmount = tokenIsCurrency0 ? amount1 : amount0;
        if (tokenAmount > tokenSupply || wethAmount != 0) revert DeploymentVerificationFailed("seed amounts");

        uint256 tokenDust = token.balanceOf(address(result.lock));
        uint256 wethDust = weth.balanceOf(address(result.lock));
        if (tokenDust > MAX_LOCKED_DUST || wethDust != 0) revert ExcessLockedDust(tokenDust, wethDust);

        uint128 positionLiquidity = positionManager.getPositionLiquidity(result.lock.positionTokenId());
        if (positionLiquidity != result.liquidity) revert DeploymentVerificationFailed("position liquidity");
        if (IERC721(address(positionManager)).ownerOf(result.lock.positionTokenId()) != address(result.lock)) {
            revert DeploymentVerificationFailed("position owner");
        }
    }

    function _poolKey(address token, address hook) internal view returns (PoolKey memory key) {
        (address currency0, address currency1) = token < address(weth) ? (token, address(weth)) : (address(weth), token);
        key = PoolKey({
            currency0: Currency.wrap(currency0),
            currency1: Currency.wrap(currency1),
            fee: POOL_LP_FEE,
            tickSpacing: POOL_TICK_SPACING,
            hooks: IHooks(hook)
        });
    }

    function _validateTokenStrings(string calldata name_, string calldata symbol_) internal pure {
        uint256 nameLength = bytes(name_).length;
        uint256 symbolLength = bytes(symbol_).length;
        if (nameLength == 0 || nameLength > MAX_NAME_LENGTH) revert InvalidNameLength(nameLength);
        if (symbolLength == 0 || symbolLength > MAX_SYMBOL_LENGTH) revert InvalidSymbolLength(symbolLength);
    }

    function _validateTokenURI(string calldata tokenURI_) internal pure {
        bytes calldata value = bytes(tokenURI_);
        uint256 length = value.length;
        if (length == 0 || length > MAX_TOKEN_URI_LENGTH) revert InvalidTokenURI(length);
        if (
            length <= 7 || value[0] != "i" || value[1] != "p" || value[2] != "f" || value[3] != "s" || value[4] != ":"
                || value[5] != "/" || value[6] != "/"
        ) {
            revert UnsupportedTokenURIScheme();
        }
        for (uint256 i = 7; i < length; ++i) {
            if (value[i] >= 0x41 && value[i] <= 0x5A) revert UppercaseTokenURI();
        }
    }
}
Chain explorer3735msChain node82ms