RootedLaunchFactory

0xb684a46c6e8258f281f740af1b92aa1925a47e66

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
15,905 bytes
ABI entries
116
35 read · 19 write
License
none

Contract information

Address
0xb684a46c6e8258f281f740af1b92aa1925a47e66
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0xa288a81452…eebf8bc4ae

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (35)

MAX_METADATA_URI_LEN()uint256
MAX_NAME_LEN()uint256
MAX_ROOTED_BASKET()uint256
MAX_SYMBOL_LEN()uint256
ROOTED_CONTRIBUTION_BPS_MAX()uint16
ROOTED_CONTRIBUTION_BPS_MIN()uint16
STUCK_RESCUE_DELAY()uint256
TOTAL_SUPPLY()uint256
bondingPoolImplementation()address
configEnabled(uint256)bool
configs(uint256)address, uint256, uint256, uint256, uint256, uint16, uint16, uint32, bool
deploymentFinalized()bool
feeCollector()address
getLaunchedToken(address)tuple
isGraduated(address)bool
lastLaunchAt(address)uint256
launchCooldown()uint32
launchPaused()bool
launchedTokens(address)address, address, address, address, address, string, uint256, uint256, uint256, bool, bool, address, uint8, uint8, uint16
marketModeOf(address)uint8
nextConfigId()uint256
nextLaunchAt(address)uint256
nextTokenSalt()uint256
owner()address
pendingOwner()address
protocolSink()address
renounceOwnership()
rootedContributionBps(address)uint16
rootedHook()address
rootedLaunchesEnabled()bool
rootedOrchestrator()address
rootedPoolId(address)bytes32
rootedTargetsOf(address)tuple[]
stuckRescueEta(address)uint256
tokenImplementation()address

Events (18)

ConfigEnabledUpdatedDeploymentFinalizedLaunchConfigRegisteredLaunchCooldownUpdatedLaunchPausedUpdatedOwnershipTransferStartedOwnershipTransferredRescuedRootedHookUpdatedRootedLaunchCreatedRootedLaunchesEnabledUpdatedRootedOrchestratorUpdatedRootedTargetConfiguredStuckPoolRescueProposedStuckPoolRescuedTokenGraduatedTokenLaunchedTokenSaltBumped

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenImplementation_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "bondingPoolImplementation_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "protocolSink_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeCollector_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyGraduated",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadMsgValue",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ConfigDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeploymentAlreadyFinalized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeploymentIncomplete",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeploymentNotFinalized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmergencyGraduationDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FailedDeployment",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidConfig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidContributionBps",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRootedBasket",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRootedInfrastructure",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "nextLaunchAt",
        "type": "uint256"
      }
    ],
    "name": "LaunchCooldownActive",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LaunchPaused",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "MetadataTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NameTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotBondingPool",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPendingGraduation",
    "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": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RenounceDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RescueFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedAlreadyBound",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedFactoryMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedHookAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedLaunchesDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedNotConfigured",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedOrchestratorAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedOrchestratorNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedOrchestratorPaused",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootedRegistrarMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StuckRescueAlreadyProposed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StuckRescueNotProposed",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "readyAt",
        "type": "uint256"
      }
    ],
    "name": "StuckRescueNotReady",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SymbolTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownConfig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UtilityAllocationsNotSupported",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "ConfigEnabledUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "DeploymentFinalized",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      }
    ],
    "name": "LaunchConfigRegistered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "cooldownSecs",
        "type": "uint32"
      }
    ],
    "name": "LaunchCooldownUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bool",
        "name": "paused",
        "type": "bool"
      }
    ],
    "name": "LaunchPausedUpdated",
    "type": "event"
  },
  {
    "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": true,
        "internalType": "address",
        "name": "asset",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "Rescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "hook",
        "type": "address"
      }
    ],
    "name": "RootedHookUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "contributionBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "rootTargetCount",
        "type": "uint256"
      }
    ],
    "name": "RootedLaunchCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "RootedLaunchesEnabledUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "orchestrator",
        "type": "address"
      }
    ],
    "name": "RootedOrchestratorUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "asset",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "targetIndex",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "targetWeightBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "priced",
        "type": "bool"
      }
    ],
    "name": "RootedTargetConfigured",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eta",
        "type": "uint256"
      }
    ],
    "name": "StuckPoolRescueProposed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recovery",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      }
    ],
    "name": "StuckPoolRescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "uniswapPool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethMigrated",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensMigrated",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensBurned",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethRefunded",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint24",
        "name": "feeUsed",
        "type": "uint24"
      }
    ],
    "name": "TokenGraduated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "bondingPool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "graduationTarget",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "initialBuyAmount",
        "type": "uint256"
      }
    ],
    "name": "TokenLaunched",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "previousSalt",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "newSalt",
        "type": "uint256"
      }
    ],
    "name": "TokenSaltBumped",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_METADATA_URI_LEN",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_NAME_LEN",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_ROOTED_BASKET",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SYMBOL_LEN",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROOTED_CONTRIBUTION_BPS_MAX",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROOTED_CONTRIBUTION_BPS_MIN",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "STUCK_RESCUE_DELAY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOTAL_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "bindRootedOrchestrator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "bondingPoolImplementation",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "newSalt",
        "type": "uint256"
      }
    ],
    "name": "bumpTokenSalt",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "completeEmergencyGraduation",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "virtualEth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "virtualToken",
        "type": "uint256"
      }
    ],
    "name": "completeGraduation",
    "outputs": [
      {
        "internalType": "address",
        "name": "liquidityVault",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "configEnabled",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "configs",
    "outputs": [
      {
        "internalType": "address",
        "name": "pairToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "graduationTarget",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "virtualEth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "virtualToken",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokensForSale",
        "type": "uint256"
      },
      {
        "internalType": "uint16",
        "name": "maxWalletBps",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "maxTxBps",
        "type": "uint16"
      },
      {
        "internalType": "uint32",
        "name": "restrictionSecs",
        "type": "uint32"
      },
      {
        "internalType": "bool",
        "name": "exists",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "deploymentFinalized",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address payable",
        "name": "recovery",
        "type": "address"
      }
    ],
    "name": "executeStuckPoolRescue",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeCollector",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "finalizeDeployment",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getLaunchedToken",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "deployer",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "bondingPool",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "uniswapPool",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "feeRecipient",
            "type": "address"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "uint256",
            "name": "configId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "graduationTarget",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "graduated",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "exists",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "allocationVault",
            "type": "address"
          },
          {
            "internalType": "enum UtilityTypes.MarketMode",
            "name": "marketMode",
            "type": "uint8"
          },
          {
            "internalType": "enum UtilityTypes.AllocationMode",
            "name": "allocationMode",
            "type": "uint8"
          },
          {
            "internalType": "uint16",
            "name": "utilityAllocationBps",
            "type": "uint16"
          }
        ],
        "internalType": "struct RootedLaunchFactory.LaunchedToken",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isGraduated",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "lastLaunchAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchCooldown",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchPaused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataUri",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "feeRecipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "configId",
            "type": "uint256"
          }
        ],
        "internalType": "struct UtilityTypes.LaunchParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "beneficiary",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "allocationBps",
            "type": "uint16"
          },
          {
            "internalType": "enum UtilityTypes.AllocationRole",
            "name": "role",
            "type": "uint8"
          }
        ],
        "internalType": "struct UtilityTypes.AllocationInput[]",
        "name": "allocations",
        "type": "tuple[]"
      },
      {
        "internalType": "uint16",
        "name": "contributionBps",
        "type": "uint16"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "asset",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "targetWeightBps",
            "type": "uint16"
          },
          {
            "internalType": "bool",
            "name": "priced",
            "type": "bool"
          }
        ],
        "internalType": "struct UtilityTypes.RootTargetInput[]",
        "name": "rootTargets",
        "type": "tuple[]"
      }
    ],
    "name": "launchRooted",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "bondingPool",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "allocationVault",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "launchedTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "bondingPool",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "uniswapPool",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "graduationTarget",
        "type": "uint256"
      },
      {
        "internalType": "bool",
        "name": "graduated",
        "type": "bool"
      },
      {
        "internalType": "bool",
        "name": "exists",
        "type": "bool"
      },
      {
        "internalType": "address",
        "name": "allocationVault",
        "type": "address"
      },
      {
        "internalType": "enum UtilityTypes.MarketMode",
        "name": "marketMode",
        "type": "uint8"
      },
      {
        "internalType": "enum UtilityTypes.AllocationMode",
        "name": "allocationMode",
        "type": "uint8"
      },
      {
        "internalType": "uint16",
        "name": "utilityAllocationBps",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "marketModeOf",
    "outputs": [
      {
        "internalType": "enum UtilityTypes.MarketMode",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextConfigId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      }
    ],
    "name": "nextLaunchAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextTokenSalt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "proposeStuckPoolRescue",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolSink",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "pairToken",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "graduationTarget",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "virtualEth",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "virtualToken",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "tokensForSale",
            "type": "uint256"
          },
          {
            "internalType": "uint16",
            "name": "maxWalletBps",
            "type": "uint16"
          },
          {
            "internalType": "uint16",
            "name": "maxTxBps",
            "type": "uint16"
          },
          {
            "internalType": "uint32",
            "name": "restrictionSecs",
            "type": "uint32"
          },
          {
            "internalType": "bool",
            "name": "exists",
            "type": "bool"
          }
        ],
        "internalType": "struct RootedLaunchFactory.LaunchConfig",
        "name": "cfg",
        "type": "tuple"
      }
    ],
    "name": "registerConfig",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "registerDefaultConfig",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "asset",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescue",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "rootedContributionBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rootedHook",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rootedLaunchesEnabled",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rootedOrchestrator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "rootedPoolId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "rootedTargetsOf",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "asset",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "targetWeightBps",
            "type": "uint16"
          },
          {
            "internalType": "bool",
            "name": "priced",
            "type": "bool"
          }
        ],
        "internalType": "struct UtilityTypes.RootTargetInput[]",
        "name": "",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "configId",
        "type": "uint256"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setConfigEnabled",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint32",
        "name": "cooldownSecs",
        "type": "uint32"
      }
    ],
    "name": "setLaunchCooldown",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bool",
        "name": "paused",
        "type": "bool"
      }
    ],
    "name": "setLaunchPaused",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "hook_",
        "type": "address"
      }
    ],
    "name": "setRootedHook",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setRootedLaunchesEnabled",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "orchestrator_",
        "type": "address"
      }
    ],
    "name": "setRootedOrchestrator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "stuckRescueEta",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenImplementation",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Config} from "./Config.sol";
import {ILaunchToken} from "./interfaces/ILaunchToken.sol";
import {IBondingPool} from "./interfaces/IBondingPool.sol";
import {ILaunchFactoryGraduation} from "./interfaces/ILaunchFactoryGraduation.sol";
import {BondingPool} from "./BondingPool.sol";
import {UtilityTypes} from "./utility/UtilityTypes.sol";
import {IWETH} from "./vendor/IWETH.sol";

/// @dev Minimal Rooted orchestrator surface (no direct rooted import — solc isolation).
interface IRootedGraduationOrchestratorLean {
    struct LaunchGraduateParams {
        address launchToken;
        address quoteToken;
        address hook;
        address creator;
        address feeCollector;
        uint256 reserveQuote;
        uint256 tokenInventory;
        uint256 virtualEth;
        uint256 virtualToken;
        uint16 contributionBps;
    }

    function launchesPaused() external view returns (bool);
    function launchFactory() external view returns (address);
    function isLaunchFactory(address factory) external view returns (bool);
    function poolManager() external view returns (address);
    function weth() external view returns (address);

    function graduateFromFactory(LaunchGraduateParams calldata p)
        external
        returns (bytes32 poolId, address capitalVault, address liquidityVault);
}

interface IRootedHookLean {
    function registrar() external view returns (address);
    function poolManager() external view returns (address);
}

/// @notice Lean Rooted-only factory: bonding curve → RootedGraduationOrchestrator.
/// @dev Separate from live Wild LaunchFactory (EIP-170). No Utility / Uniswap V3 graduation.
contract RootedLaunchFactory is Ownable2Step, ReentrancyGuard, ILaunchFactoryGraduation {
    using Clones for address;
    using SafeERC20 for IERC20;

    error ZeroAddress();
    error InvalidConfig();
    error UnknownConfig();
    error MetadataTooLong();
    error LaunchCooldownActive(uint256 nextLaunchAt);
    error UnknownToken();
    error AlreadyGraduated();
    error NotBondingPool();
    error BadMsgValue();
    error RescueFailed();
    error DeploymentNotFinalized();
    error DeploymentAlreadyFinalized();
    error DeploymentIncomplete();
    error NameTooLong();
    error SymbolTooLong();
    error RenounceDisabled();
    error LaunchPaused();
    error ConfigDisabled();
    error RootedOrchestratorAlreadySet();
    error RootedOrchestratorNotSet();
    error RootedAlreadyBound();
    error RootedLaunchesDisabled();
    error RootedNotConfigured();
    error RootedOrchestratorPaused();
    error InvalidRootedBasket();
    error InvalidContributionBps();
    error RootedHookAlreadySet();
    error InvalidRootedInfrastructure();
    error RootedFactoryMismatch();
    error RootedRegistrarMismatch();
    error UtilityAllocationsNotSupported();
    error EmergencyGraduationDisabled();
    error StuckRescueNotProposed();
    error StuckRescueNotReady(uint256 readyAt);
    error StuckRescueAlreadyProposed();
    error NotPendingGraduation();

    event TokenLaunched(
        address indexed token,
        address indexed deployer,
        address indexed bondingPool,
        address feeRecipient,
        string metadataURI,
        uint256 configId,
        uint256 graduationTarget,
        uint256 initialBuyAmount
    );

    event TokenGraduated(
        address indexed token,
        address indexed uniswapPool,
        uint256 positionId,
        uint256 ethMigrated,
        uint256 tokensMigrated,
        uint256 tokensBurned,
        uint256 ethRefunded,
        uint24 feeUsed
    );

    event LaunchConfigRegistered(uint256 indexed configId);
    event LaunchCooldownUpdated(uint32 cooldownSecs);
    event DeploymentFinalized();
    event Rescued(address indexed asset, address indexed to, uint256 amount);
    event TokenSaltBumped(uint256 indexed previousSalt, uint256 indexed newSalt);
    event LaunchPausedUpdated(bool paused);
    event ConfigEnabledUpdated(uint256 indexed configId, bool enabled);
    event RootedOrchestratorUpdated(address indexed orchestrator);
    event RootedHookUpdated(address indexed hook);
    event RootedLaunchesEnabledUpdated(bool enabled);
    event RootedLaunchCreated(
        address indexed token,
        address indexed deployer,
        uint16 contributionBps,
        uint256 rootTargetCount
    );
    event RootedTargetConfigured(
        address indexed token,
        address indexed asset,
        uint256 targetIndex,
        uint16 targetWeightBps,
        bool priced
    );
    event StuckPoolRescueProposed(address indexed token, uint256 eta);
    event StuckPoolRescued(
        address indexed token,
        address indexed recovery,
        uint256 ethAmount,
        uint256 tokenAmount
    );

    uint256 public constant MAX_ROOTED_BASKET = 10;
    /// @notice Minimum delay between propose and execute for stuck-graduation abort.
    uint256 public constant STUCK_RESCUE_DELAY = 3 days;
    uint16 public constant ROOTED_CONTRIBUTION_BPS_MIN = 100;
    uint16 public constant ROOTED_CONTRIBUTION_BPS_MAX = 500;
    uint256 public constant TOTAL_SUPPLY = Config.TOTAL_SUPPLY;
    uint256 public constant MAX_METADATA_URI_LEN = 2048;
    uint256 public constant MAX_NAME_LEN = 64;
    uint256 public constant MAX_SYMBOL_LEN = 16;

    struct LaunchConfig {
        address pairToken;
        uint256 graduationTarget;
        uint256 virtualEth;
        uint256 virtualToken;
        uint256 tokensForSale;
        uint16 maxWalletBps;
        uint16 maxTxBps;
        uint32 restrictionSecs;
        bool exists;
    }

    /// @dev Field layout matches Wild LaunchFactory for shared FE ABI decoding.
    struct LaunchedToken {
        address token;
        address deployer;
        address bondingPool;
        address uniswapPool;
        address feeRecipient;
        string metadataURI;
        uint256 configId;
        uint256 positionId;
        uint256 graduationTarget;
        bool graduated;
        bool exists;
        address allocationVault;
        UtilityTypes.MarketMode marketMode;
        UtilityTypes.AllocationMode allocationMode;
        uint16 utilityAllocationBps;
    }

    address public immutable tokenImplementation;
    address public immutable bondingPoolImplementation;
    address public immutable protocolSink;
    /// @notice Passed to Rooted liquidity vault as feeCollector (Safe or protocol sink).
    address public immutable feeCollector;

    address public rootedOrchestrator;
    address public rootedHook;
    bool public rootedLaunchesEnabled;
    mapping(address => uint16) public rootedContributionBps;
    mapping(address => UtilityTypes.RootTargetInput[]) private _rootedTargets;
    mapping(address => bytes32) public rootedPoolId;

    uint256 public nextConfigId;
    mapping(uint256 => LaunchConfig) public configs;
    mapping(address => LaunchedToken) public launchedTokens;

    uint32 public launchCooldown = Config.DEFAULT_LAUNCH_COOLDOWN;
    mapping(address => uint256) public lastLaunchAt;

    bool public deploymentFinalized;
    bool public launchPaused;
    mapping(uint256 => bool) public configEnabled;

    uint256 public nextTokenSalt;

    /// @dev Earliest timestamp owner may execute stuck-pool rescue (0 = not proposed).
    mapping(address => uint256) public stuckRescueEta;

    constructor(
        address tokenImplementation_,
        address bondingPoolImplementation_,
        address protocolSink_,
        address feeCollector_,
        address owner_
    ) Ownable(owner_) {
        if (
            tokenImplementation_ == address(0) || bondingPoolImplementation_ == address(0)
                || protocolSink_ == address(0) || feeCollector_ == address(0)
        ) {
            revert ZeroAddress();
        }
        tokenImplementation = tokenImplementation_;
        bondingPoolImplementation = bondingPoolImplementation_;
        protocolSink = protocolSink_;
        feeCollector = feeCollector_;
    }

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

    function finalizeDeployment() external onlyOwner {
        if (deploymentFinalized) revert DeploymentAlreadyFinalized();
        if (rootedOrchestrator == address(0) || rootedHook == address(0) || nextConfigId == 0) {
            revert DeploymentIncomplete();
        }
        _validateRootedInfrastructure();
        for (uint256 i = 0; i < nextConfigId; i++) {
            if (configs[i].exists) _validateLaunchConfig(configs[i]);
        }
        deploymentFinalized = true;
        emit DeploymentFinalized();
    }

    function setLaunchPaused(bool paused) external onlyOwner {
        launchPaused = paused;
        emit LaunchPausedUpdated(paused);
    }

    function setConfigEnabled(uint256 configId, bool enabled) external onlyOwner {
        if (!configs[configId].exists) revert UnknownConfig();
        configEnabled[configId] = enabled;
        emit ConfigEnabledUpdated(configId, enabled);
    }

    function setLaunchCooldown(uint32 cooldownSecs) external onlyOwner {
        launchCooldown = cooldownSecs;
        emit LaunchCooldownUpdated(cooldownSecs);
    }

    function bumpTokenSalt(uint256 newSalt) external onlyOwner {
        uint256 previous = nextTokenSalt;
        nextTokenSalt = newSalt;
        emit TokenSaltBumped(previous, newSalt);
    }

    function setRootedOrchestrator(address orchestrator_) external onlyOwner {
        if (orchestrator_ == address(0)) revert ZeroAddress();
        if (rootedOrchestrator != address(0)) revert RootedOrchestratorAlreadySet();
        if (orchestrator_.code.length == 0) revert InvalidRootedInfrastructure();
        if (!IRootedGraduationOrchestratorLean(orchestrator_).isLaunchFactory(address(this))) {
            revert RootedFactoryMismatch();
        }
        rootedOrchestrator = orchestrator_;
        emit RootedOrchestratorUpdated(orchestrator_);
    }

    function setRootedHook(address hook_) external onlyOwner {
        if (hook_ == address(0)) revert ZeroAddress();
        if (rootedHook != address(0)) revert RootedHookAlreadySet();
        if (hook_.code.length == 0) revert InvalidRootedInfrastructure();
        address orch = rootedOrchestrator;
        if (orch == address(0)) revert RootedNotConfigured();
        if (IRootedHookLean(hook_).registrar() != orch) revert RootedRegistrarMismatch();
        rootedHook = hook_;
        emit RootedHookUpdated(hook_);
    }

    function setRootedLaunchesEnabled(bool enabled) external onlyOwner {
        if (enabled) _validateRootedInfrastructure();
        rootedLaunchesEnabled = enabled;
        emit RootedLaunchesEnabledUpdated(enabled);
    }

    function bindRootedOrchestrator(address token) external onlyOwner {
        if (rootedOrchestrator == address(0)) revert RootedOrchestratorNotSet();
        LaunchedToken storage info = launchedTokens[token];
        if (!info.exists) revert UnknownToken();
        if (ILaunchToken(token).rootedOrchestrator() != address(0)) revert RootedAlreadyBound();
        if (ILaunchToken(token).marketMode() != uint8(UtilityTypes.MarketMode.ROOTED)) {
            ILaunchToken(token).setMarketMode(uint8(UtilityTypes.MarketMode.ROOTED));
        }
        ILaunchToken(token).setRootedOrchestrator(rootedOrchestrator);
    }

    function rootedTargetsOf(address token)
        external
        view
        returns (UtilityTypes.RootTargetInput[] memory)
    {
        return _rootedTargets[token];
    }

    function marketModeOf(address) external pure returns (UtilityTypes.MarketMode) {
        return UtilityTypes.MarketMode.ROOTED;
    }

    function isGraduated(address token) external view returns (bool) {
        return launchedTokens[token].graduated;
    }

    function nextLaunchAt(address deployer) public view returns (uint256) {
        uint256 last = lastLaunchAt[deployer];
        if (last == 0 || launchCooldown == 0) return 0;
        uint256 readyAt = last + launchCooldown;
        return block.timestamp >= readyAt ? 0 : readyAt;
    }

    /// @notice Register the canonical mainnet launch config (pairToken = Config.WETH, 20 ETH target).
    /// @dev Mainnet-only. On testnet, Safe must `registerConfig` with `pairToken = orchestrator.weth()`
    ///      and a cheap `graduationTarget` (≥ TESTNET_MIN_GRADUATION_TARGET) so LaunchToken.pairToken()
    ///      stays accurate for tooling/indexers.
    function registerDefaultConfig() external onlyOwner returns (uint256 configId) {
        if (block.chainid != Config.CHAIN_ID) revert InvalidConfig();
        return registerConfig(
            LaunchConfig({
                pairToken: Config.WETH,
                graduationTarget: Config.DEFAULT_GRADUATION_TARGET,
                virtualEth: Config.DEFAULT_VIRTUAL_ETH,
                virtualToken: Config.DEFAULT_VIRTUAL_TOKEN,
                tokensForSale: Config.DEFAULT_TOKENS_FOR_SALE,
                maxWalletBps: Config.DEFAULT_MAX_WALLET_BPS,
                maxTxBps: Config.DEFAULT_MAX_TX_BPS,
                restrictionSecs: Config.DEFAULT_RESTRICTION_SECS,
                exists: true
            })
        );
    }

    function registerConfig(LaunchConfig memory cfg) public onlyOwner returns (uint256 configId) {
        _validateLaunchConfig(cfg);
        cfg.exists = true;
        configId = nextConfigId++;
        configs[configId] = cfg;
        configEnabled[configId] = true;
        emit LaunchConfigRegistered(configId);
    }

    function getLaunchedToken(address token) external view returns (LaunchedToken memory) {
        return launchedTokens[token];
    }

    /// @notice Rooted create: bonding curve + contribution + basket intent (no Utility).
    function launchRooted(
        UtilityTypes.LaunchParams calldata params,
        UtilityTypes.AllocationInput[] calldata allocations,
        uint16 contributionBps,
        UtilityTypes.RootTargetInput[] calldata rootTargets
    ) external payable nonReentrant returns (address token, address bondingPool, address allocationVault) {
        if (!deploymentFinalized) revert DeploymentNotFinalized();
        if (launchPaused) revert LaunchPaused();
        if (!rootedLaunchesEnabled) revert RootedLaunchesDisabled();
        _validateRootedInfrastructure();
        if (IRootedGraduationOrchestratorLean(rootedOrchestrator).launchesPaused()) {
            revert RootedOrchestratorPaused();
        }
        if (allocations.length != 0) revert UtilityAllocationsNotSupported();
        _validateRootedIntent(contributionBps, rootTargets);

        uint256 readyAt = nextLaunchAt(msg.sender);
        if (readyAt != 0) revert LaunchCooldownActive(readyAt);

        LaunchConfig memory cfg = configs[params.configId];
        if (!cfg.exists) revert UnknownConfig();
        if (!configEnabled[params.configId]) revert ConfigDisabled();
        if (bytes(params.name).length == 0 || bytes(params.name).length > MAX_NAME_LEN) {
            revert NameTooLong();
        }
        if (bytes(params.symbol).length == 0 || bytes(params.symbol).length > MAX_SYMBOL_LEN) {
            revert SymbolTooLong();
        }
        if (bytes(params.metadataUri).length > MAX_METADATA_URI_LEN) revert MetadataTooLong();

        address feeRecipient = params.feeRecipient == address(0) ? msg.sender : params.feeRecipient;
        uint256 initialBuyAmount = msg.value;

        uint256 saltEntropy = nextTokenSalt;
        bytes32 salt = keccak256(abi.encode(msg.sender, saltEntropy, block.number));
        nextTokenSalt = saltEntropy + 1;

        token = tokenImplementation.cloneDeterministic(salt);
        bondingPool = bondingPoolImplementation.clone();
        allocationVault = address(0);

        _initializeLaunchPair(
            token, bondingPool, params.name, params.symbol, feeRecipient, cfg
        );

        launchedTokens[token] = LaunchedToken({
            token: token,
            deployer: msg.sender,
            bondingPool: bondingPool,
            uniswapPool: address(0),
            feeRecipient: feeRecipient,
            metadataURI: params.metadataUri,
            configId: params.configId,
            positionId: 0,
            graduationTarget: cfg.graduationTarget,
            graduated: false,
            exists: true,
            allocationVault: address(0),
            marketMode: UtilityTypes.MarketMode.ROOTED,
            allocationMode: UtilityTypes.AllocationMode.FAIR,
            utilityAllocationBps: 0
        });

        rootedContributionBps[token] = contributionBps;
        for (uint256 i; i < rootTargets.length; ++i) {
            _rootedTargets[token].push(rootTargets[i]);
        }

        lastLaunchAt[msg.sender] = block.timestamp;

        if (initialBuyAmount > 0) {
            _executeInitialBuy(bondingPool, cfg.graduationTarget, initialBuyAmount);
        }

        emit TokenLaunched(
            token,
            msg.sender,
            bondingPool,
            feeRecipient,
            params.metadataUri,
            params.configId,
            cfg.graduationTarget,
            initialBuyAmount
        );
        emit RootedLaunchCreated(token, msg.sender, contributionBps, rootTargets.length);
        for (uint256 i; i < rootTargets.length; ++i) {
            UtilityTypes.RootTargetInput calldata target = rootTargets[i];
            emit RootedTargetConfigured(
                token, target.asset, i, target.targetWeightBps, target.priced
            );
        }
    }

    function completeGraduation(
        address token,
        uint256 ethAmount,
        uint256 tokenAmount,
        uint256 virtualEth,
        uint256 virtualToken
    ) external payable override nonReentrant returns (address liquidityVault, uint256 positionId) {
        return _completeRootedGraduation(token, ethAmount, tokenAmount, virtualEth, virtualToken);
    }

    function completeEmergencyGraduation(
        address,
        uint256,
        uint256,
        uint256,
        uint256
    ) external payable override returns (address, uint256) {
        // V3 emergency adapter path is intentionally disabled for Rooted.
        // Use proposeStuckPoolRescue / executeStuckPoolRescue instead.
        revert EmergencyGraduationDisabled();
    }

    /// @notice Start timelock to abort a Rooted pool stuck in `graduationPending`.
    function proposeStuckPoolRescue(address token) external onlyOwner {
        LaunchedToken storage info = launchedTokens[token];
        if (!info.exists) revert UnknownToken();
        if (info.graduated) revert AlreadyGraduated();
        BondingPool pool = BondingPool(payable(info.bondingPool));
        if (!pool.graduationPending() || pool.graduated()) revert NotPendingGraduation();
        if (stuckRescueEta[token] != 0) revert StuckRescueAlreadyProposed();
        uint256 eta = block.timestamp + STUCK_RESCUE_DELAY;
        stuckRescueEta[token] = eta;
        emit StuckPoolRescueProposed(token, eta);
    }

    /// @notice After delay, pull stuck ETH + tokens to `recovery` for ops-managed refunds.
    function executeStuckPoolRescue(address token, address payable recovery)
        external
        onlyOwner
        returns (uint256 ethAmount, uint256 tokenAmount)
    {
        if (recovery == address(0)) revert ZeroAddress();
        LaunchedToken storage info = launchedTokens[token];
        if (!info.exists) revert UnknownToken();
        if (info.graduated) revert AlreadyGraduated();
        uint256 eta = stuckRescueEta[token];
        if (eta == 0) revert StuckRescueNotProposed();
        if (block.timestamp < eta) revert StuckRescueNotReady(eta);

        BondingPool pool = BondingPool(payable(info.bondingPool));
        if (!pool.graduationPending() || pool.graduated()) revert NotPendingGraduation();

        (ethAmount, tokenAmount) = pool.factoryAbortPendingGraduation(recovery, recovery);
        info.graduated = true;
        info.uniswapPool = address(0);
        delete stuckRescueEta[token];
        emit StuckPoolRescued(token, recovery, ethAmount, tokenAmount);
    }

    function rescue(address asset, address to, uint256 amount) external onlyOwner {
        if (to == address(0)) revert ZeroAddress();
        if (asset == address(0)) {
            (bool ok,) = to.call{value: amount}("");
            if (!ok) revert RescueFailed();
        } else {
            IERC20(asset).safeTransfer(to, amount);
        }
        emit Rescued(asset, to, amount);
    }

    function _completeRootedGraduation(
        address token,
        uint256 ethAmount,
        uint256 tokenAmount,
        uint256 virtualEth,
        uint256 virtualToken
    ) private returns (address liquidityVault, uint256 positionId) {
        LaunchedToken storage info = launchedTokens[token];
        if (!info.exists) revert UnknownToken();
        if (info.graduated) revert AlreadyGraduated();
        if (msg.sender != info.bondingPool) revert NotBondingPool();
        if (msg.value != ethAmount) revert BadMsgValue();

        address orch = rootedOrchestrator;
        address hook = rootedHook;
        if (orch == address(0) || hook == address(0)) revert RootedNotConfigured();
        if (IRootedGraduationOrchestratorLean(orch).launchesPaused()) {
            revert RootedOrchestratorPaused();
        }

        uint16 contributionBps = rootedContributionBps[token];
        if (
            contributionBps < ROOTED_CONTRIBUTION_BPS_MIN
                || contributionBps > ROOTED_CONTRIBUTION_BPS_MAX
        ) {
            revert InvalidContributionBps();
        }

        address weth = IRootedGraduationOrchestratorLean(orch).weth();
        IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);
        IWETH(weth).deposit{value: ethAmount}();
        IERC20(weth).forceApprove(orch, ethAmount);
        IERC20(token).forceApprove(orch, tokenAmount);

        bytes32 poolId;
        (poolId,, liquidityVault) = IRootedGraduationOrchestratorLean(orch).graduateFromFactory(
            IRootedGraduationOrchestratorLean.LaunchGraduateParams({
                launchToken: token,
                quoteToken: weth,
                hook: hook,
                creator: info.deployer,
                feeCollector: feeCollector,
                reserveQuote: ethAmount,
                tokenInventory: tokenAmount,
                virtualEth: virtualEth,
                virtualToken: virtualToken,
                contributionBps: contributionBps
            })
        );
        IERC20(weth).forceApprove(orch, 0);
        IERC20(token).forceApprove(orch, 0);

        rootedPoolId[token] = poolId;
        info.uniswapPool = liquidityVault;
        info.positionId = 0;
        info.graduated = true;
        positionId = 0;

        emit TokenGraduated(token, liquidityVault, 0, ethAmount, tokenAmount, 0, 0, 0);
    }

    function _validateRootedIntent(
        uint16 contributionBps,
        UtilityTypes.RootTargetInput[] calldata rootTargets
    ) private pure {
        if (
            contributionBps < ROOTED_CONTRIBUTION_BPS_MIN
                || contributionBps > ROOTED_CONTRIBUTION_BPS_MAX
        ) {
            revert InvalidContributionBps();
        }
        uint256 n = rootTargets.length;
        if (n == 0 || n > MAX_ROOTED_BASKET) revert InvalidRootedBasket();
        uint256 sum;
        for (uint256 i; i < n; ++i) {
            address asset = rootTargets[i].asset;
            if (asset == address(0)) revert InvalidRootedBasket();
            if (rootTargets[i].targetWeightBps == 0) revert InvalidRootedBasket();
            sum += rootTargets[i].targetWeightBps;
            for (uint256 j; j < i; ++j) {
                if (rootTargets[j].asset == asset) revert InvalidRootedBasket();
            }
        }
        if (sum != 10_000) revert InvalidRootedBasket();
    }

    function _executeInitialBuy(
        address bondingPool,
        uint256 graduationTarget,
        uint256 initialBuyAmount
    ) private {
        uint256 balanceBefore = address(this).balance - initialBuyAmount;
        uint256 ethIn = _netEthInForBuy(initialBuyAmount, graduationTarget);
        uint256 expected = BondingPool(payable(bondingPool)).getTokensOut(ethIn);
        uint256 forSale = BondingPool(payable(bondingPool)).tokensForSale();
        if (expected > forSale) expected = forSale;
        uint256 minOut = (expected * 9_900) / 10_000;
        IBondingPool(bondingPool).buy{value: initialBuyAmount}(msg.sender, minOut);
        uint256 refund = address(this).balance - balanceBefore;
        if (refund > 0) {
            (bool ok,) = msg.sender.call{value: refund}("");
            if (!ok) revert RescueFailed();
        }
    }

    function _validateRootedInfrastructure() private view {
        address orch = rootedOrchestrator;
        address hook = rootedHook;
        if (orch == address(0) || hook == address(0)) revert RootedNotConfigured();
        if (orch.code.length == 0 || hook.code.length == 0) {
            revert InvalidRootedInfrastructure();
        }
        if (!IRootedGraduationOrchestratorLean(orch).isLaunchFactory(address(this))) {
            revert RootedFactoryMismatch();
        }
        if (IRootedHookLean(hook).registrar() != orch) revert RootedRegistrarMismatch();
        if (
            IRootedHookLean(hook).poolManager()
                != IRootedGraduationOrchestratorLean(orch).poolManager()
        ) {
            revert InvalidRootedInfrastructure();
        }
    }

    function _initializeLaunchPair(
        address token,
        address bondingPool,
        string memory name_,
        string memory symbol_,
        address feeRecipient,
        LaunchConfig memory cfg
    ) private {
        uint256 maxWallet = (TOTAL_SUPPLY * cfg.maxWalletBps) / 10_000;
        uint256 maxTx = (TOTAL_SUPPLY * cfg.maxTxBps) / 10_000;

        ILaunchToken(token).initialize(
            name_,
            symbol_,
            address(this),
            bondingPool,
            cfg.pairToken,
            Config.DEFAULT_POOL_FEE,
            feeRecipient,
            maxWallet,
            maxTx,
            0,
            TOTAL_SUPPLY
        );
        ILaunchToken(token).setBondingPool(bondingPool);

        IBondingPool(bondingPool).initialize(
            token,
            address(this),
            msg.sender,
            feeRecipient,
            protocolSink,
            cfg.graduationTarget,
            cfg.virtualEth,
            cfg.virtualToken,
            cfg.tokensForSale
        );

        ILaunchToken(token).setMarketMode(uint8(UtilityTypes.MarketMode.ROOTED));
        ILaunchToken(token).setRootedOrchestrator(rootedOrchestrator);
    }

    function _validateLaunchConfig(LaunchConfig memory cfg) private view {
        if (cfg.pairToken == address(0)) revert ZeroAddress();
        if (cfg.graduationTarget == 0) revert InvalidConfig();
        if (cfg.virtualEth == 0 || cfg.virtualToken == 0 || cfg.tokensForSale == 0) {
            revert InvalidConfig();
        }
        if (cfg.virtualEth < Config.MIN_VIRTUAL_ETH) revert InvalidConfig();
        if (cfg.virtualToken < Config.MIN_VIRTUAL_TOKEN) revert InvalidConfig();
        uint256 minGraduationTarget = block.chainid == Config.TESTNET_CHAIN_ID
            ? Config.TESTNET_MIN_GRADUATION_TARGET
            : Config.MIN_GRADUATION_TARGET;
        if (cfg.graduationTarget < minGraduationTarget) revert InvalidConfig();
        if (cfg.tokensForSale >= TOTAL_SUPPLY) revert InvalidConfig();
        if (cfg.tokensForSale >= cfg.virtualToken) revert InvalidConfig();
        {
            uint256 terminalVe = cfg.virtualEth + cfg.graduationTarget;
            uint256 tokensSoldAtTarget =
                Math.mulDiv(cfg.graduationTarget, cfg.virtualToken, terminalVe);
            if (tokensSoldAtTarget > cfg.tokensForSale) revert InvalidConfig();
        }
        if (cfg.maxWalletBps == 0 || cfg.maxWalletBps > 10_000) revert InvalidConfig();
        if (cfg.maxTxBps == 0 || cfg.maxTxBps > 10_000) revert InvalidConfig();
        if (cfg.restrictionSecs == 0) revert InvalidConfig();
    }

    function _netEthInForBuy(uint256 msgValue, uint256 room) private pure returns (uint256 ethIn) {
        uint256 feeEth = (msgValue * Config.PRE_GRAD_TRADE_FEE_BPS) / 10_000;
        ethIn = msgValue - feeEth;
        if (ethIn > room) {
            ethIn = room;
            feeEth = (ethIn * Config.PRE_GRAD_TRADE_FEE_BPS)
                / (10_000 - Config.PRE_GRAD_TRADE_FEE_BPS);
            uint256 used = ethIn + feeEth;
            if (used > msgValue) {
                feeEth = (msgValue * Config.PRE_GRAD_TRADE_FEE_BPS) / 10_000;
                ethIn = msgValue - feeEth;
                if (ethIn > room) ethIn = room;
            }
        }
    }

    receive() external payable {}
}
Chain explorer7645msChain node101ms