MintFastLaunchpad

0xd61998ae9b29e1f19dfb70ba890bc85895c83f1b

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
23,375 bytes
ABI entries
97
36 read · 14 write
License
none

Contract information

Address
0xd61998ae9b29e1f19dfb70ba890bc85895c83f1b
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x92fc26a4bc…b736031d82

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (36)

BPS()uint256
DEAD()address
KIND_GITHUB()uint8
KIND_WALLET()uint8
KIND_X()uint8
MAX_BENEFICIARIES()uint256
MAX_CREATION_FEE()uint256
MIN_TICK()int24
POOL_FEE()uint24
TICK_SPACING()int24
TOTAL_SUPPLY()uint256
beneficiaries(address)tuple[]
beneficiariesLocked(address)bool
beneficiaryCount(address)uint256
boundarySqrtPriceX96()uint160
boundaryTick()int24
createPaused()bool
creationFee()uint256
defaultLpSplitBps()uint16
defaultSocialSplitBps()uint16
feeVault()address
hook()address
isFeeManager(address)bool
lpPosition(address)bytes32, int24, int24, uint128
owner()address
pendingEth(address)uint256
pendingOwner()address
poolIdOf(address)bytes32
poolManager()address
protocolFeesAccrued()uint256
renounceOwnership()
state(address)tuple, bytes32, uint256
tokenNonce()uint256
tokens(address)address, uint40, uint16, uint16, bool
totalPendingEth()uint256
treasury()address

Events (15)

BeneficiariesClearedBeneficiariesUpdatedBeneficiaryRegisteredCreatePausedSetCreationFeeSetDefaultFeesSetFeeManagerSetFeesDistributedHarvestedLaunchedOwnershipTransferStartedOwnershipTransferredProtocolFeesSweptTokenCreatedTreasurySet

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "contract ISocialFeeVault",
        "name": "feeVault_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "hookSalt",
        "type": "bytes32"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BadBeneficiary",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadCallback",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadMetadata",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BeneficiariesLocked",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BpsSumMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CreationPaused",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeadlineExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DirectEthRejected",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DuplicateBeneficiary",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FeeTooHigh",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientCreationFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidHandle",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidPlatform",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LaunchGriefed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAuthorized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NothingToHarvest",
    "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": "OwnershipRenounced",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PoolNotLive",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "R",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Slippage",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SplitTooHigh",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "T",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TooManyBeneficiaries",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "BeneficiariesCleared",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "count",
        "type": "uint256"
      }
    ],
    "name": "BeneficiariesUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "key",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "kind",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "handle",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "wallet",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "BeneficiaryRegistered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bool",
        "name": "paused",
        "type": "bool"
      }
    ],
    "name": "CreatePausedSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "CreationFeeSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "socialSplitBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "lpSplitBps",
        "type": "uint16"
      }
    ],
    "name": "DefaultFeesSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "FeeManagerSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "key",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "FeesDistributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethCollected",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "devEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolEth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "liquidityMinted",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethReinvested",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "pendingEthAfter",
        "type": "uint256"
      }
    ],
    "name": "Harvested",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      }
    ],
    "name": "Launched",
    "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": "treasury",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ProtocolFeesSwept",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "beneficiaryCount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "beneficiariesLocked",
        "type": "bool"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "socialSplitBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "lpSplitBps",
        "type": "uint16"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      }
    ],
    "name": "TreasurySet",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEAD",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "KIND_GITHUB",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "KIND_WALLET",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "KIND_X",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_BENEFICIARIES",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_CREATION_FEE",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MIN_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POOL_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "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": "beneficiaries",
    "outputs": [
      {
        "components": [
          {
            "internalType": "bytes32",
            "name": "key",
            "type": "bytes32"
          },
          {
            "internalType": "uint16",
            "name": "bps",
            "type": "uint16"
          }
        ],
        "internalType": "struct MintFastLaunchpad.Beneficiary[]",
        "name": "",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "beneficiariesLocked",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "beneficiaryCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "boundarySqrtPriceX96",
    "outputs": [
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "boundaryTick",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "minTokensOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "buy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "createPaused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "components": [
          {
            "internalType": "uint8",
            "name": "kind",
            "type": "uint8"
          },
          {
            "internalType": "string",
            "name": "handle",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "wallet",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "bps",
            "type": "uint16"
          }
        ],
        "internalType": "struct MintFastLaunchpad.BeneficiaryInput[]",
        "name": "beneficiaries",
        "type": "tuple[]"
      },
      {
        "internalType": "uint256",
        "name": "initialBuyMinTokensOut",
        "type": "uint256"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creationFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultLpSplitBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultSocialSplitBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeVault",
    "outputs": [
      {
        "internalType": "contract ISocialFeeVault",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "harvest",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "devEth",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hook",
    "outputs": [
      {
        "internalType": "contract MintFastHook",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "isFeeManager",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "lpPosition",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "pendingEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "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": "poolIdOf",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolFeesAccrued",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minEthOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "sell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "uint8",
            "name": "kind",
            "type": "uint8"
          },
          {
            "internalType": "string",
            "name": "handle",
            "type": "string"
          },
          {
            "internalType": "address",
            "name": "wallet",
            "type": "address"
          },
          {
            "internalType": "uint16",
            "name": "bps",
            "type": "uint16"
          }
        ],
        "internalType": "struct MintFastLaunchpad.BeneficiaryInput[]",
        "name": "inputs",
        "type": "tuple[]"
      }
    ],
    "name": "setBeneficiaries",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bool",
        "name": "paused",
        "type": "bool"
      }
    ],
    "name": "setCreatePaused",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "setCreationFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "socialSplitBps",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "lpSplitBps",
        "type": "uint16"
      }
    ],
    "name": "setDefaultFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "setFeeManager",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      }
    ],
    "name": "setTreasury",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "state",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint40",
            "name": "createdAt",
            "type": "uint40"
          },
          {
            "internalType": "uint16",
            "name": "socialSplitBps",
            "type": "uint16"
          },
          {
            "internalType": "uint16",
            "name": "lpSplitBps",
            "type": "uint16"
          },
          {
            "internalType": "bool",
            "name": "beneficiariesLocked",
            "type": "bool"
          }
        ],
        "internalType": "struct MintFastLaunchpad.TokenInfo",
        "name": "info",
        "type": "tuple"
      },
      {
        "internalType": "bytes32",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "spotPriceWad",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sweepProtocolFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenNonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "tokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "uint40",
        "name": "createdAt",
        "type": "uint40"
      },
      {
        "internalType": "uint16",
        "name": "socialSplitBps",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "lpSplitBps",
        "type": "uint16"
      },
      {
        "internalType": "bool",
        "name": "beneficiariesLocked",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalPendingEth",
    "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": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

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 {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";

import {MintFastToken} from "./MintFastToken.sol";
import {MintFastHook} from "./MintFastHook.sol";
import {HandleLib} from "./libraries/HandleLib.sol";
import {ISocialFeeVault} from "./interfaces/ISocialFeeVault.sol";
import {
    BalanceDelta,
    BalanceDeltaLib,
    Currency,
    CurrencyLib,
    IPoolManager,
    IUnlockCallback,
    ModifyLiquidityParams,
    PoolId,
    PoolIdLib,
    PoolKey,
    SwapParams,
    V4StateReader
} from "./interfaces/IUniswapV4.sol";
import {TickMath} from "./vendor/v3/TickMath.sol";
import {LiquidityAmounts} from "./vendor/v3/LiquidityAmounts.sol";

/// @title MintFastLaunchpad
/// @notice AMM-native launchpad for Robinhood Chain: every token launches DIRECTLY into
///         its own Uniswap v4 pool (0% LP fee + the MintFastHook) against NATIVE ETH.
///         The entire 1B supply is minted as a single-sided position owned by this
///         contract, so the pool itself plays the bonding curve: ETH-per-token starts
///         at ~1.5 ETH market cap and climbs as people buy (token-per-ETH descends
///         through the locked tick range). There is no separate curve phase, no
///         graduation mechanism, and no moment where this contract custodies raw ETH
///         reserves beyond its accounted earmarks.
///
///         ETH is currency0 of every pool (address(0) sorts first), which fixes the
///         token ordering once and for all: the token is ALWAYS currency1.
///
/// Fee model (split defaults snapshotted per token at creation):
///  - The pool's own LP fee is ZERO. The MintFastHook (deployed by this contract's
///    constructor, attached to every pool) charges 2% of the ETH side of every swap
///    on buys AND sells, in ETH only, and accrues it per token. Exact-in buys charge
///    2% of the FULL ETH input in beforeSwap (including any unfilled remainder on a
///    partial fill); every other swap shape charges 2% of ETH actually moved.
///  - Permissionless harvest() pulls the accrued ETH and splits it: 50% escrows to
///    the token's Fletchers, 25% is added to the pool as ETH-side-only liquidity
///    (a spacing-aligned range between the current price and the launch boundary,
///    pure buy-side depth, locked forever), 25% to the protocol. Per swap that is
///    1% dev / 0.5% liquidity / 0.5% protocol, in ETH, for the life of the token.
///    ETH that cannot yet be placed (price within one spacing of the launch boundary)
///    stays in pendingEth until the spot moves back into a reinvestable range. It is
///    never swept to the protocol.
///
/// The "listing cap" (a USD-pegged market-cap milestone that fluctuates with the ETH
/// price) is a UI/indexer concept, deliberately NOT on-chain, so it can track the
/// real ETH price without an oracle dependency.
///
/// Owner powers (the complete list; everything else is immutable by construction):
///  - pause/unpause NEW token creation (never affects existing tokens)
///  - set creationFee (hard-capped), future creations only
///  - set default socialSplitBps / lpSplitBps (sum-capped), snapshotted per token
///  - set treasury (receives protocol fees via pull-payment only)
///  - add/remove fee managers (can rewrite a token's Fletcher beneficiaries after lock)
///
/// Fletcher beneficiary locking:
///  - Empty beneficiary list at create → 100% to creator wallet, UNLOCKED. Creator may
///    call setBeneficiaries once; that call locks forever.
///  - Non-empty list at create → locked immediately.
///  - Fee managers may call setBeneficiaries on any live token (always leaves it locked).
///  - setBeneficiaries auto-harvests pending hook fees / LP earmarks to the OLD set
///    before swapping recipients; already-escrowed vault balances stay under old keys.
///    Only fees accrued AFTER the change use the new set. socialSplitBps / lpSplitBps
///    remain immutable per-token snapshots.
///
/// The owner can NOT: freeze trading (it lives on Uniswap), seize anything, mint,
/// alter splits on live tokens, touch vault escrow, or withdraw locked liquidity.
/// There is no code path that removes position liquidity (modifyLiquidity is only
/// ever called with a delta >= 0). No proxies exist.
contract MintFastLaunchpad is Ownable2Step, ReentrancyGuard, IUnlockCallback {
    using FixedPointMathLib for uint256;
    using BalanceDeltaLib for BalanceDelta;
    using PoolIdLib for PoolKey;

    // ─── Types ─────────────────────────────────────────────────────────────────

    /// @dev slot 1: 160 + 40 + 16 + 16 + 8 = 240. Splits are immutable; beneficiariesLocked
    ///      flips false→true once (create with named list, creator one-shot, or fee manager).
    struct TokenInfo {
        address creator;
        uint40 createdAt;
        uint16 socialSplitBps; // share of harvested fees for the Fletchers (the "dev cut")
        uint16 lpSplitBps; // share of harvested fees reinvested into the locked liquidity
        bool beneficiariesLocked; // true => only fee managers may rewrite recipients
    }

    /// @notice A resolved fee beneficiary. `key` is a SocialFeeVault escrow key:
    ///         X/GitHub -> identityKey (claimed via attestation); WALLET -> walletKey
    ///         (claimed by the address with no signature).
    struct Beneficiary {
        bytes32 key;
        uint16 bps; // share of the creator-allocatable cut; all beneficiaries sum to 10000
    }

    /// @notice Creation-time beneficiary description (calldata form).
    struct BeneficiaryInput {
        uint8 kind; // 1 = X, 2 = GitHub, 3 = Wallet
        string handle; // normalized handle for X/GitHub; "" for Wallet
        address wallet; // payout address for Wallet; address(0) for X/GitHub
        uint16 bps; // share of the creator-allocatable cut
    }

    /// @dev unlockCallback dispatch.
    enum Action {
        LAUNCH,
        BUY,
        SELL,
        REINVEST
    }

    // ─── Constants ─────────────────────────────────────────────────────────────

    uint256 public constant BPS = 10_000;
    uint256 public constant MAX_CREATION_FEE = 0.005 ether;
    uint8 public constant KIND_X = 1;
    uint8 public constant KIND_GITHUB = 2;
    uint8 public constant KIND_WALLET = 3;
    uint256 public constant MAX_BENEFICIARIES = 8;
    address public constant DEAD = 0x000000000000000000000000000000000000dEaD;

    uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;

    /// @dev Uniswap v4: the pool's own LP fee is ZERO. The 2% ETH fee lives in the
    ///      MintFastHook attached to every pool instead.
    uint24 public constant POOL_FEE = 0;
    int24 public constant TICK_SPACING = 200;
    /// @dev Widest spacing-aligned lower bound (full-range on the buy side).
    int24 public constant MIN_TICK = -887_200;

    /// @dev Launch price: 1.5e-9 ETH per token (3/2e9) => ~1.5 ETH initial market cap.
    ///      As a v4 price (currency1 per currency0 = token per ETH) that is DEN/NUM.
    uint256 internal constant START_PRICE_NUM = 3;
    uint256 internal constant START_PRICE_DEN = 2e9;

    // ─── Immutables ────────────────────────────────────────────────────────────

    IPoolManager public immutable poolManager;
    ISocialFeeVault public immutable feeVault;
    /// @notice The fee hook attached to every launchpad pool (deployed by this
    ///         contract's constructor via CREATE2. The salt is mined so the address
    ///         carries the v4 permission bits).
    MintFastHook public immutable hook;

    /// @dev Every pool starts at the same boundary: the launch price snapped to tick
    ///      spacing away from the token side, so the initial mint is guaranteed
    ///      single-sided. The locked range is [MIN_TICK, boundaryTick] and the price
    ///      descends through it (in token-per-ETH terms) as people buy.
    int24 public immutable boundaryTick;
    uint160 public immutable boundarySqrtPriceX96;

    // ─── Storage ───────────────────────────────────────────────────────────────

    address public treasury;
    uint256 public creationFee = 0.0005 ether;
    uint16 public defaultSocialSplitBps = 5000; // 50% of harvested fees → dev/Fletchers
    uint16 public defaultLpSplitBps = 2500; // 25% → back into the coin's liquidity
    bool public createPaused;
    uint256 public protocolFeesAccrued; // pull-payment to treasury
    uint256 public tokenNonce;
    /// @notice Sum of every token's pendingEth, the slice of this contract's ETH
    ///         balance already earmarked for liquidity reinvestment. Together with
    ///         protocolFeesAccrued it accounts for the ENTIRE resting ETH balance:
    ///         address(this).balance == protocolFeesAccrued + totalPendingEth
    ///         whenever no flow is mid-flight.
    uint256 public totalPendingEth;

    mapping(address token => TokenInfo) public tokens;
    /// @notice Per-token ETH earmarked for liquidity but not yet placeable (no
    ///         spacing-aligned room between the current price and the launch
    ///         boundary, or liquidity-math dust), carried into the next harvest.
    mapping(address token => uint256) public pendingEth;
    mapping(address token => Beneficiary[]) internal _beneficiaries;
    /// @notice Protocol operators who may rewrite Fletcher beneficiaries on any token
    ///         (including after lock). Owner add/remove via setFeeManager.
    mapping(address account => bool) public isFeeManager;

    // ─── Events ────────────────────────────────────────────────────────────────

    event TokenCreated(
        address indexed token,
        address indexed creator,
        string name,
        string symbol,
        string metadataURI,
        uint256 beneficiaryCount,
        bool beneficiariesLocked,
        uint16 socialSplitBps,
        uint16 lpSplitBps
    );
    /// @dev Same tx as TokenCreated, after the full supply is locked into the pool.
    ///      `poolId` keys the v4 singleton's Swap events for this token's pool.
    event Launched(
        address indexed token,
        bytes32 indexed poolId,
        uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    );
    /// @dev One per beneficiary, same tx as TokenCreated. For WALLET, `handle` is "" and
    ///      `wallet` is the payout address; for X/GitHub, `wallet` is address(0). The
    ///      default (no beneficiaries named) emits a single WALLET beneficiary = creator.
    event BeneficiaryRegistered(
        address indexed token, bytes32 indexed key, uint8 kind, string handle, address wallet, uint16 bps
    );
    /// @dev Lifetime-fee collection from the fee hook (ETH only). `devEth` also lands
    ///      in the vault as per-beneficiary Deposited events in the same tx.
    ///      `liquidityMinted` is Uniswap liquidity units; `ethReinvested` is the ETH
    ///      actually settled into the new position (0 when the earmark stays pending).
    ///      `pendingEthAfter` is the token's remaining LP earmark after this harvest.
    event Harvested(
        address indexed token,
        address indexed caller,
        uint256 ethCollected,
        uint256 devEth,
        uint256 protocolEth,
        uint256 liquidityMinted,
        uint256 ethReinvested,
        uint256 pendingEthAfter
    );
    /// @dev One per Fletcher share when the social cut is escrowed. Indexers use this
    ///      for exact per-token attribution; vault Deposited stays key-only.
    event FeesDistributed(address indexed token, bytes32 indexed key, uint256 amount);
    event CreationFeeSet(uint256 fee);
    event DefaultFeesSet(uint16 socialSplitBps, uint16 lpSplitBps);
    event TreasurySet(address treasury);
    event CreatePausedSet(bool paused);
    event ProtocolFeesSwept(address indexed treasury, uint256 amount);
    /// @dev Emitted before re-registering beneficiaries on setBeneficiaries. Indexers
    ///      should delete the token's prior beneficiary rows, then apply the following
    ///      BeneficiaryRegistered logs in the same tx.
    event BeneficiariesCleared(address indexed token);
    event BeneficiariesUpdated(address indexed token, address indexed caller, uint256 count);
    event FeeManagerSet(address indexed account, bool allowed);

    // ─── Errors ────────────────────────────────────────────────────────────────

    error CreationPaused();
    error InsufficientCreationFee();
    error BadMetadata();
    error UnknownToken();
    error DeadlineExpired();
    error ZeroAmount();
    error Slippage();
    error NothingToHarvest();
    error BadCallback();
    error DirectEthRejected();
    error TransferFailed();
    error ZeroAddress();
    error FeeTooHigh();
    error SplitTooHigh();
    error TooManyBeneficiaries();
    error BadBeneficiary();
    error BpsSumMismatch();
    error PoolNotLive();
    error LaunchGriefed();
    error NotAuthorized();
    error BeneficiariesLocked();
    error DuplicateBeneficiary();
    error OwnershipRenounced();

    /// @param hookSalt CREATE2 salt for the fee hook, mined off-chain so the hook
    ///        address carries the v4 permission bits (see HookMiner). The hook's
    ///        constructor self-checks the flags and reverts on a bad salt.
    constructor(IPoolManager poolManager_, ISocialFeeVault feeVault_, address treasury_, bytes32 hookSalt)
        Ownable(msg.sender)
    {
        if (address(poolManager_) == address(0) || address(feeVault_) == address(0) || treasury_ == address(0)) {
            revert ZeroAddress();
        }
        poolManager = poolManager_;
        feeVault = feeVault_;
        treasury = treasury_;
        hook = new MintFastHook{salt: hookSalt}(poolManager_, address(this));

        // token-per-ETH launch price, snapped down to the tick spacing (away from the
        // token side: a lower token-per-ETH tick means a token price >= launch price)
        uint160 targetSqrtP =
            uint160(FixedPointMathLib.sqrt(START_PRICE_DEN.fullMulDiv(1 << 192, START_PRICE_NUM)));
        int24 boundary = _floorToSpacing(TickMath.getTickAtSqrtRatio(targetSqrtP));
        boundaryTick = boundary;
        boundarySqrtPriceX96 = TickMath.getSqrtRatioAtTick(boundary);
    }

    /// @dev The PoolManager pays out native ETH with plain transfers (take), both
    ///      for pool flows and when the fee hook redeems collected fees during
    ///      harvest; nothing else may send ETH outside the payable entrypoints.
    receive() external payable {
        if (msg.sender != address(poolManager)) revert DirectEthRejected();
    }

    // ─── Create ────────────────────────────────────────────────────────────────

    /// @notice Launch a token straight into its own Uniswap v4 pool. `msg.value` =
    ///         creationFee + optional atomic first buy. The full supply is locked as
    ///         single-sided liquidity from ~1.5 ETH market cap upward, tradable by
    ///         anyone, on any interface, immediately.
    /// @param beneficiaries how the Fletcher share of harvested fees is divided. Each is
    ///        an X handle, a GitHub handle, or a wallet address, with a bps share; the
    ///        shares must sum to 10000. Pass an EMPTY array to send 100% to the creator
    ///        (the "dev"), claimable via SocialFeeVault.claimWallet. That path leaves
    ///        beneficiaries unlocked so the creator may reassign once. Any non-empty
    ///        list locks immediately. Max 8 beneficiaries.
    /// @param initialBuyMinTokensOut slippage floor for the atomic first buy (0 if none)
    /// @param salt caller entropy mixed into the CREATE2 salt so same-block pool grief
    ///        cannot cheaply target a predictable token address (retry with a new salt)
    function createToken(
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        BeneficiaryInput[] calldata beneficiaries,
        uint256 initialBuyMinTokensOut,
        bytes32 salt
    ) external payable nonReentrant returns (address token) {
        if (createPaused) revert CreationPaused();
        if (msg.value < creationFee) revert InsufficientCreationFee();
        if (
            bytes(name).length == 0 || bytes(name).length > 32 || bytes(symbol).length == 0
                || bytes(symbol).length > 10 || bytes(metadataURI).length > 256
        ) revert BadMetadata();

        uint256 nonce = ++tokenNonce;
        // block.number + caller salt keep the address unpredictable enough that a
        // pool-pre-initialization grief can't permanently pin one creator (see _launch),
        // and a failed creation can simply be retried with a fresh salt / next block.
        token = address(
            new MintFastToken{
                salt: keccak256(abi.encode(msg.sender, nonce, block.chainid, block.number, salt))
            }(name, symbol, metadataURI)
        );

        TokenInfo storage info = tokens[token];
        info.creator = msg.sender;
        info.createdAt = uint40(block.timestamp);
        info.socialSplitBps = defaultSocialSplitBps;
        info.lpSplitBps = defaultLpSplitBps;
        // Named list at create → locked immediately. Empty → unlocked (creator one-shot).
        info.beneficiariesLocked = beneficiaries.length > 0;

        protocolFeesAccrued += creationFee;

        uint256 count = _registerBeneficiaries(token, beneficiaries, msg.sender);
        emit TokenCreated(
            token,
            msg.sender,
            name,
            symbol,
            metadataURI,
            count,
            info.beneficiariesLocked,
            info.socialSplitBps,
            info.lpSplitBps
        );

        _launch(token);

        uint256 buyEth = msg.value - creationFee;
        if (buyEth > 0) {
            _buy(token, buyEth, initialBuyMinTokensOut, msg.sender);
        }
    }

    /// @notice Replace a token's Fletcher beneficiaries. Creator may call once while
    ///         unlocked (empty-list creates). Fee managers may call anytime. Always
    ///         locks afterward. `inputs` must be non-empty (use an explicit wallet /
    ///         social row. Empty is only valid at createToken). Pending fees are
    ///         harvested to the current beneficiaries before the list is replaced.
    function setBeneficiaries(address token, BeneficiaryInput[] calldata inputs) external nonReentrant {
        TokenInfo storage info = tokens[token];
        if (info.creator == address(0)) revert UnknownToken();
        if (inputs.length == 0) revert BadBeneficiary();

        if (isFeeManager[msg.sender]) {
            // protocol operator, allowed even when locked
        } else if (msg.sender == info.creator) {
            if (info.beneficiariesLocked) revert BeneficiariesLocked();
        } else {
            revert NotAuthorized();
        }

        // Flush pending fees to the OLD set before swapping recipients.
        if (hook.pendingFees(token) != 0 || pendingEth[token] != 0) _harvest(token);

        _clearBeneficiaries(token);
        uint256 count = _registerBeneficiaries(token, inputs, info.creator);
        info.beneficiariesLocked = true;
        emit BeneficiariesUpdated(token, msg.sender, count);
    }

    /// @dev Initializes the pool at the launch boundary and locks the entire supply as
    ///      a single-sided position. External pre-initialization of our PoolKey is
    ///      blocked by MintFastHook.beforeInitialize (launchpad-only). Keep the
    ///      try/catch + push-back / LaunchGriefed path as defense in depth if spot is
    ///      ever off-boundary after init (e.g. odd retry); do not remove without the
    ///      hook gate:
    ///       - pre-initialized on the ETH side of the boundary (token cheaper than
    ///         launch): harmless. Nobody can own the token yet, so no token-side
    ///         liquidity can exist; the first buy free-jumps the price to our range.
    ///       - pre-initialized on the token side (token pricier than launch): we push
    ///         the price back toward the boundary by selling supply into whatever
    ///         ETH-side liquidity was posted. Their ETH ends up locked in OUR
    ///         position, and then mint with both balances at the corrected price.
    ///      Only this contract may call PoolManager.initialize for our hook pools.
    function _launch(address token) private {
        PoolKey memory key = _poolKey(token);
        try poolManager.initialize(key, boundarySqrtPriceX96) returns (int24) {}
        catch {
            // pool already initialized (defense in depth); push-back absorbs below
        }
        PoolId id = key.toId();
        (uint160 sqrtP,) = V4StateReader.getSlot0(poolManager, id);
        if (sqrtP == 0) revert PoolNotLive();

        bytes memory res = poolManager.unlock(abi.encode(Action.LAUNCH, abi.encode(token, sqrtP)));
        (uint128 liquidity, uint160 finalSqrtP) = abi.decode(res, (uint128, uint160));

        emit Launched(token, PoolId.unwrap(id), finalSqrtP, MIN_TICK, boundaryTick, liquidity);
    }

    /// @dev Grief push-back + supply lock. Runs inside the PoolManager unlock.
    function _launchCallback(bytes memory payload) private returns (bytes memory) {
        (address token, uint160 sqrtP) = abi.decode(payload, (address, uint160));
        PoolKey memory key = _poolKey(token);

        uint256 arbEth;
        if (sqrtP < boundarySqrtPriceX96) {
            // price sits inside our range (token side): sell supply through the hostile
            // liquidity to drag it back to the boundary
            BalanceDelta d = poolManager.swap(
                key,
                SwapParams({
                    zeroForOne: false,
                    amountSpecified: -int256(TOTAL_SUPPLY),
                    sqrtPriceLimitX96: boundarySqrtPriceX96
                }),
                ""
            );
            uint256 sold = _neg(d.amount1());
            if (sold > 0) _settleToken(token, sold);
            arbEth = _pos(d.amount0());
            if (arbEth > 0) poolManager.take(CurrencyLib.NATIVE, address(this), arbEth);
            (sqrtP,) = V4StateReader.getSlot0(poolManager, key.toId());
        }

        // Defense in depth (external pre-init is blocked by the hook). If spot is still
        // below the boundary after push-back, or the entire supply was sold into depth
        // (tokenBal == 0), fail cleanly instead of underflowing on `tokenBal - 1`.
        uint256 tokenBal = MintFastToken(token).balanceOf(address(this));
        if (sqrtP < boundarySqrtPriceX96 || tokenBal == 0) revert LaunchGriefed();

        // Lock everything we hold (the supply, plus any ETH arbed off a griefer). One
        // wei per side is held back from the liquidity math: v4 rounds principal
        // charges up, so the charge can exceed the computed amount by a wei. The
        // held-back wei guarantees settlement never overdraws this flow's budget.
        // Both weis just join the pending dust and reinvest at the first harvest.
        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
            sqrtP,
            TickMath.getSqrtRatioAtTick(MIN_TICK),
            TickMath.getSqrtRatioAtTick(boundaryTick),
            arbEth == 0 ? 0 : arbEth - 1,
            tokenBal - 1
        );
        if (liquidity == 0) revert ZeroAmount();
        (BalanceDelta cd,) = poolManager.modifyLiquidity(
            key,
            ModifyLiquidityParams({
                tickLower: MIN_TICK,
                tickUpper: boundaryTick,
                liquidityDelta: int256(uint256(liquidity)),
                salt: bytes32(0)
            }),
            ""
        );
        uint256 owedEth = _neg(cd.amount0());
        uint256 owedToken = _neg(cd.amount1());
        // Cap vs budget so ethDust / settle never assume a charge beyond what we hold.
        // With the 1-wei holdback this is a no-op; if rounding ever overshoots, settling
        // only the budgeted amount fails the unlock cleanly instead of bricking later.
        if (owedEth > arbEth) owedEth = arbEth;
        if (owedToken > tokenBal) owedToken = tokenBal;
        if (owedEth > 0) poolManager.settle{value: owedEth}();
        if (owedToken > 0) _settleToken(token, owedToken);

        // token-side liquidity-math dust is unusable now that fees (and reinvests)
        // are ETH-only. Burn it; ETH dust (arbed off a griefer) earmarks for the
        // first harvest's ETH-side reinvest
        uint256 tokenDust = MintFastToken(token).balanceOf(address(this));
        if (tokenDust > 0) MintFastToken(token).transfer(DEAD, tokenDust);
        uint256 ethDust = arbEth - owedEth;
        pendingEth[token] = ethDust;
        totalPendingEth += ethDust;

        return abi.encode(liquidity, sqrtP);
    }

    function _floorToSpacing(int24 tick) private pure returns (int24) {
        int24 d = tick / TICK_SPACING;
        if (tick % TICK_SPACING < 0) d--;
        return d * TICK_SPACING;
    }

    /// @dev The canonical PoolKey for a launched token: native ETH is currency0 of
    ///      every pool (address(0) sorts below any token), 0% LP fee, the fee hook.
    ///      Fully derivable; nothing about the pool needs storage.
    function _poolKey(address token) private view returns (PoolKey memory) {
        return PoolKey({
            currency0: CurrencyLib.NATIVE,
            currency1: Currency.wrap(token),
            fee: POOL_FEE,
            tickSpacing: TICK_SPACING,
            hooks: address(hook)
        });
    }

    /// @dev Wipe stored beneficiaries and signal indexers before a re-register.
    function _clearBeneficiaries(address token) private {
        delete _beneficiaries[token];
        emit BeneficiariesCleared(token);
    }

    /// @dev Validates + stores the beneficiary set and emits one event per beneficiary.
    ///      Empty input => a single WALLET beneficiary = `defaultWallet` at 100%.
    ///      `defaultWallet` must be the token creator (not msg.sender) so fee-manager
    ///      updates never accidentally key fees to the operator.
    function _registerBeneficiaries(
        address token,
        BeneficiaryInput[] calldata inputs,
        address defaultWallet
    ) private returns (uint256) {
        Beneficiary[] storage stored = _beneficiaries[token];

        if (inputs.length == 0) {
            bytes32 key = HandleLib.deriveWalletKey(defaultWallet);
            stored.push(Beneficiary({key: key, bps: uint16(BPS)}));
            emit BeneficiaryRegistered(token, key, KIND_WALLET, "", defaultWallet, uint16(BPS));
            return 1;
        }

        if (inputs.length > MAX_BENEFICIARIES) revert TooManyBeneficiaries();
        uint256 sumBps;
        for (uint256 i; i < inputs.length; ++i) {
            BeneficiaryInput calldata b = inputs[i];
            if (b.bps == 0) revert BadBeneficiary();
            sumBps += b.bps;

            bytes32 key;
            if (b.kind == KIND_X || b.kind == KIND_GITHUB) {
                if (b.wallet != address(0)) revert BadBeneficiary();
                HandleLib.validate(b.kind, b.handle);
                key = HandleLib.deriveIdentityKey(b.kind, HandleLib.hashHandle(b.handle));
            } else if (b.kind == KIND_WALLET) {
                if (b.wallet == address(0) || bytes(b.handle).length != 0) revert BadBeneficiary();
                key = HandleLib.deriveWalletKey(b.wallet);
            } else {
                revert BadBeneficiary();
            }

            for (uint256 j; j < i; ++j) {
                if (stored[j].key == key) revert DuplicateBeneficiary();
            }

            stored.push(Beneficiary({key: key, bps: b.bps}));
            emit BeneficiaryRegistered(token, key, b.kind, b.handle, b.wallet, b.bps);
        }
        if (sumBps != BPS) revert BpsSumMismatch();
        return inputs.length;
    }

    /// @dev Splits the creator-allocatable fee cut across the token's beneficiaries.
    ///      The last beneficiary absorbs any rounding remainder so the sum is exact.
    function _distributeCreatorCut(address token, uint256 creatorCut) private {
        if (creatorCut == 0) return;
        Beneficiary[] storage bens = _beneficiaries[token];
        uint256 n = bens.length;
        uint256 distributed;
        for (uint256 i; i < n; ++i) {
            uint256 share = (i + 1 == n) ? creatorCut - distributed : creatorCut.mulDiv(bens[i].bps, BPS);
            distributed += share;
            if (share != 0) {
                emit FeesDistributed(token, bens[i].key, share);
                feeVault.deposit{value: share}(bens[i].key);
            }
        }
    }

    // Trade: thin wrappers over the pool, no launchpad surcharge beyond MintFastHook, no router

    /// @notice Buy `token` with ETH through its pool. Tokens go straight to the caller;
    ///         if the buy would exhaust the range (partial fill), the unspent ETH is
    ///         refunded. Identical to swapping on any Uniswap v4 interface.
    function buy(address token, uint256 minTokensOut, uint256 deadline)
        external
        payable
        nonReentrant
        returns (uint256 tokensOut)
    {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (msg.value == 0) revert ZeroAmount();
        tokensOut = _buy(token, msg.value, minTokensOut, msg.sender);
    }

    function _buy(address token, uint256 ethIn, uint256 minTokensOut, address recipient)
        private
        returns (uint256 tokensOut)
    {
        if (tokens[token].creator == address(0)) revert UnknownToken();

        bytes memory res =
            poolManager.unlock(abi.encode(Action.BUY, abi.encode(token, ethIn, recipient)));
        (uint256 ethUsed, uint256 out) = abi.decode(res, (uint256, uint256));
        tokensOut = out;
        if (tokensOut < minTokensOut) revert Slippage();

        // partial fill (price ran past the locked range): refund the unspent ETH
        if (ethUsed < ethIn) {
            (bool ok,) = recipient.call{value: ethIn - ethUsed}("");
            if (!ok) revert TransferFailed();
        }
    }

    function _buyCallback(bytes memory payload) private returns (bytes memory) {
        (address token, uint256 ethIn, address recipient) =
            abi.decode(payload, (address, uint256, address));
        BalanceDelta d = poolManager.swap(
            _poolKey(token),
            SwapParams({
                zeroForOne: true, // ETH in
                amountSpecified: -int256(ethIn), // negative = exact input
                sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1
            }),
            ""
        );
        uint256 ethUsed = _neg(d.amount0());
        uint256 tokensOut = _pos(d.amount1());
        if (tokensOut == 0) revert ZeroAmount();
        poolManager.settle{value: ethUsed}();
        poolManager.take(Currency.wrap(token), recipient, tokensOut);
        return abi.encode(ethUsed, tokensOut);
    }

    /// @notice Sell `token` for ETH through its pool. Requires prior ERC20 approval.
    function sell(address token, uint256 tokenAmount, uint256 minEthOut, uint256 deadline)
        external
        nonReentrant
        returns (uint256 ethOut)
    {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (tokenAmount == 0) revert ZeroAmount();
        if (tokens[token].creator == address(0)) revert UnknownToken();

        MintFastToken(token).transferFrom(msg.sender, address(this), tokenAmount);

        bytes memory res =
            poolManager.unlock(abi.encode(Action.SELL, abi.encode(token, tokenAmount)));
        (uint256 tokenUsed, uint256 out) = abi.decode(res, (uint256, uint256));
        ethOut = out;
        if (ethOut < minEthOut) revert Slippage();

        // if the pool couldn't absorb everything (no bid-side liquidity left), return
        // the unsold remainder rather than leaving it stranded here
        if (tokenUsed < tokenAmount) {
            MintFastToken(token).transfer(msg.sender, tokenAmount - tokenUsed);
        }

        (bool ok,) = msg.sender.call{value: ethOut}("");
        if (!ok) revert TransferFailed();
    }

    function _sellCallback(bytes memory payload) private returns (bytes memory) {
        (address token, uint256 tokenAmount) = abi.decode(payload, (address, uint256));
        BalanceDelta d = poolManager.swap(
            _poolKey(token),
            SwapParams({
                zeroForOne: false, // token in
                amountSpecified: -int256(tokenAmount),
                sqrtPriceLimitX96: TickMath.MAX_SQRT_RATIO - 1
            }),
            ""
        );
        uint256 tokenUsed = _neg(d.amount1());
        uint256 ethOut = _pos(d.amount0());
        if (ethOut == 0) revert ZeroAmount();
        _settleToken(token, tokenUsed);
        poolManager.take(CurrencyLib.NATIVE, address(this), ethOut);
        return abi.encode(tokenUsed, ethOut);
    }

    // ─── Lifetime fees ─────────────────────────────────────────────────────────

    /// @notice Pull the token's accrued hook fees (ETH only) and split them:
    ///         socialSplitBps → Fletcher escrow, lpSplitBps → added to the pool as
    ///         ETH-side-only locked liquidity, remainder → protocol (pull-payment).
    ///         Permissionless; ETH that can't be placed yet (no spacing-aligned room
    ///         below the launch boundary, or liquidity-math dust) carries into the
    ///         next harvest as the token's pendingEth earmark.
    function harvest(address token) external nonReentrant returns (uint256 devEth) {
        if (tokens[token].creator == address(0)) revert UnknownToken();
        return _harvest(token);
    }

    /// @dev Shared harvest body for `harvest` and `setBeneficiaries`. Caller must
    ///      already have verified the token exists and hold the reentrancy lock.
    ///      Reverts `NothingToHarvest` when there is neither fresh hook ETH nor an
    ///      LP earmark. `setBeneficiaries` pre-checks so it never hits that path.
    function _harvest(address token) private returns (uint256 devEth) {
        TokenInfo storage info = tokens[token];

        uint256 fresh = hook.collect(token); // ETH lands in this contract (or 0)

        devEth = fresh.mulDiv(info.socialSplitBps, BPS);
        uint256 lpEthFresh = fresh.mulDiv(info.lpSplitBps, BPS);
        uint256 protoEth = fresh - devEth - lpEthFresh;

        uint256 lpEth = lpEthFresh + pendingEth[token];
        if (fresh == 0 && lpEth == 0) revert NothingToHarvest();

        // Reinvest the LP share as ETH-side-only liquidity (needs >1 wei: one wei is
        // held back from the liquidity math so the rounded-up charge can never
        // overdraw this token's earmark).
        uint256 usedEth;
        uint128 minted;
        if (lpEth > 1) {
            bytes memory res =
                poolManager.unlock(abi.encode(Action.REINVEST, abi.encode(token, lpEth)));
            (usedEth, minted) = abi.decode(res, (uint256, uint128));
        }

        // usedEth is already capped to lpEth inside _reinvestCallback; belt-and-suspenders
        // so pendingEth / totalPendingEth accounting can never underflow.
        if (usedEth > lpEth) usedEth = lpEth;

        totalPendingEth = totalPendingEth + lpEthFresh - usedEth;
        pendingEth[token] = lpEth - usedEth;
        protocolFeesAccrued += protoEth;

        emit Harvested(token, msg.sender, fresh, devEth, protoEth, minted, usedEth, pendingEth[token]);

        _distributeCreatorCut(token, devEth);
    }

    /// @dev Mint the LP share as a single-sided ETH (currency0) position in the
    ///      spacing-aligned range strictly above the current tick, capped at the
    ///      launch boundary, pure buy-side depth between the current price and the
    ///      launch price. If the price sits within one spacing of the boundary (a
    ///      fresh or fully-exited pool) there is no room and the ETH stays pending
    ///      until the spot moves back into range (never routed to the protocol).
    ///      Runs inside the PoolManager unlock. Never removes liquidity.
    function _reinvestCallback(bytes memory payload) private returns (bytes memory) {
        (address token, uint256 lpEth) = abi.decode(payload, (address, uint256));
        PoolKey memory key = _poolKey(token);

        (uint160 sqrtP, int24 tick) = V4StateReader.getSlot0(poolManager, key.toId());
        int24 lower = _floorToSpacing(tick) + TICK_SPACING;
        if (lower >= boundaryTick) return abi.encode(uint256(0), uint128(0));

        uint128 minted = LiquidityAmounts.getLiquidityForAmounts(
            sqrtP,
            TickMath.getSqrtRatioAtTick(lower),
            TickMath.getSqrtRatioAtTick(boundaryTick),
            lpEth - 1, // one-wei holdback: the round-up charge can exceed the floor by a wei
            0
        );
        if (minted == 0) return abi.encode(uint256(0), uint128(0));

        (BalanceDelta cd,) = poolManager.modifyLiquidity(
            key,
            ModifyLiquidityParams({
                tickLower: lower,
                tickUpper: boundaryTick,
                liquidityDelta: int256(uint256(minted)),
                salt: bytes32(0)
            }),
            ""
        );
        // the range is strictly above the current tick, so the charge is ETH-only
        uint256 usedEth = _neg(cd.amount0());
        // Cap vs earmark so pendingEth = lpEth - usedEth never underflows. With the
        // 1-wei holdback this is a no-op; if rounding ever overshoots, settling only
        // the earmark fails the unlock cleanly instead of bricking future harvests.
        if (usedEth > lpEth) usedEth = lpEth;
        if (usedEth > 0) poolManager.settle{value: usedEth}();

        return abi.encode(usedEth, minted);
    }

    // ─── Uniswap v4 unlock callback ────────────────────────────────────────────

    /// @dev Only the PoolManager may call this, and it only ever runs inside an unlock
    ///      this contract itself initiated (v4 calls back the msg.sender of unlock()).
    function unlockCallback(bytes calldata data) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert BadCallback();
        (Action action, bytes memory payload) = abi.decode(data, (Action, bytes));
        if (action == Action.LAUNCH) return _launchCallback(payload);
        if (action == Action.BUY) return _buyCallback(payload);
        if (action == Action.SELL) return _sellCallback(payload);
        return _reinvestCallback(payload);
    }

    /// @dev ERC20 settlement against the PoolManager: sync, transfer in, settle.
    function _settleToken(address token, uint256 amount) private {
        poolManager.sync(Currency.wrap(token));
        MintFastToken(token).transfer(address(poolManager), amount);
        poolManager.settle();
    }

    function _pos(int128 x) private pure returns (uint256) {
        return x > 0 ? uint256(uint128(x)) : 0;
    }

    function _neg(int128 x) private pure returns (uint256) {
        return x < 0 ? uint256(uint128(-x)) : 0;
    }

    // ─── Protocol fees (pull-payment) ──────────────────────────────────────────

    /// @notice Push accrued protocol fees to the treasury. Callable by anyone; a reverting
    ///         treasury can never brick anything because fees only accrue in a counter.
    function sweepProtocolFees() external nonReentrant {
        uint256 amount = protocolFeesAccrued;
        if (amount == 0) revert ZeroAmount();
        protocolFeesAccrued = 0;
        address to = treasury;
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert TransferFailed();
        emit ProtocolFeesSwept(to, amount);
    }

    // ─── Views ─────────────────────────────────────────────────────────────────

    /// @notice The v4 PoolId of a token's canonical pool (keys the singleton's Swap
    ///         events); the zero id for unknown tokens.
    function poolIdOf(address token) public view returns (bytes32) {
        if (tokens[token].creator == address(0)) return bytes32(0);
        return PoolId.unwrap(_poolKey(token).toId());
    }

    /// @notice Live launch state: creator/config plus the pool's spot price in ETH per
    ///         token (wad). Market cap = spotPriceWad * 1e9 (fixed supply).
    function state(address token)
        external
        view
        returns (TokenInfo memory info, bytes32 poolId, uint256 spotPriceWad)
    {
        info = tokens[token];
        if (info.creator != address(0)) {
            PoolId id = _poolKey(token).toId();
            poolId = PoolId.unwrap(id);
            (uint160 sqrtP,) = V4StateReader.getSlot0(poolManager, id);
            spotPriceWad = _sqrtPToEthPerTokenWad(sqrtP);
        }
    }

    /// @dev The pool price is token-per-ETH (the token is always currency1); invert to
    ///      ETH-per-token wad. Full-precision mulDivs keep this exact across the range.
    function _sqrtPToEthPerTokenWad(uint160 sqrtP) private pure returns (uint256) {
        // ratioX128 = sqrtP^2 / 2^64  (token per ETH, Q128)
        uint256 ratioX128 = uint256(sqrtP).fullMulDiv(sqrtP, 1 << 64);
        if (ratioX128 == 0) return 0;
        return uint256(1 << 128).fullMulDiv(1e18, ratioX128);
    }

    /// @notice The primary locked v4 position (the launch supply lock): poolId, range,
    ///         and live liquidity. Harvest reinvests mint ADDITIONAL single-sided ETH
    ///         positions in sub-ranges of [MIN_TICK, boundaryTick], not included here.
    ///         Unharvested fees live in the hook: hook.pendingFees(token).
    function lpPosition(address token)
        external
        view
        returns (bytes32 poolId, int24 tickLower, int24 tickUpper, uint128 liquidity)
    {
        if (tokens[token].creator == address(0)) return (bytes32(0), 0, 0, 0);
        PoolId id = _poolKey(token).toId();
        poolId = PoolId.unwrap(id);
        tickLower = MIN_TICK;
        tickUpper = boundaryTick;
        liquidity = V4StateReader.getPositionLiquidity(
            poolManager, id, address(this), MIN_TICK, boundaryTick, bytes32(0)
        );
    }

    /// @notice The token's fee beneficiaries (resolved escrow keys + bps shares).
    function beneficiaries(address token) external view returns (Beneficiary[] memory) {
        return _beneficiaries[token];
    }

    function beneficiaryCount(address token) external view returns (uint256) {
        return _beneficiaries[token].length;
    }

    /// @notice Whether Fletcher recipients are locked (only fee managers may change).
    function beneficiariesLocked(address token) external view returns (bool) {
        return tokens[token].beneficiariesLocked;
    }

    // ─── Admin (bounded; see contract NatSpec for the complete power list) ─────

    /// @dev Accidental renounce would permanently freeze fee-manager / treasury /
    ///      default-fee admin. Trading and claims would still work, but support ops
    ///      would brick, so disallow it.
    function renounceOwnership() public pure override {
        revert OwnershipRenounced();
    }

    function setFeeManager(address account, bool allowed) external onlyOwner {
        if (account == address(0)) revert ZeroAddress();
        isFeeManager[account] = allowed;
        emit FeeManagerSet(account, allowed);
    }

    function setTreasury(address treasury_) external onlyOwner {
        if (treasury_ == address(0)) revert ZeroAddress();
        treasury = treasury_;
        emit TreasurySet(treasury_);
    }

    function setCreationFee(uint256 fee) external onlyOwner {
        if (fee > MAX_CREATION_FEE) revert FeeTooHigh();
        creationFee = fee;
        emit CreationFeeSet(fee);
    }

    function setDefaultFees(uint16 socialSplitBps, uint16 lpSplitBps) external onlyOwner {
        if (uint256(socialSplitBps) + uint256(lpSplitBps) > BPS) revert SplitTooHigh();
        defaultSocialSplitBps = socialSplitBps;
        defaultLpSplitBps = lpSplitBps;
        emit DefaultFeesSet(socialSplitBps, lpSplitBps);
    }

    function setCreatePaused(bool paused) external onlyOwner {
        createPaused = paused;
        emit CreatePausedSet(paused);
    }
}
Chain explorer3806msChain node91ms