SushiLaunchpad

0x30dd6230ead9312d5d00ad58ef6ef6a0093b0554

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
16,413 bytes
ABI entries
78
12 read · 11 write
License
none

Contract information

Address
0x30dd6230ead9312d5d00ad58ef6ef6a0093b0554
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Enabled
Creation tx
0x8cb9c05029…6e7dcd2964

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (12)

defaultSushiFeeBps()uint16
launchFee()uint256
launchInfo(address)tuple
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
pendingOwner()address
positionManager()address
protocolRecipient()address
protocolReserveBps()uint16
renounceOwnership()
sushiFeeBps(address)uint16
v3Factory()address

Events (12)

DefaultSushiFeeBpsUpdatedFeesDistributedLaunchFeeUpdatedLaunchFeesWithdrawnOwnershipTransferStartedOwnershipTransferredPositionCreatedProtocolRecipientUpdatedProtocolReserveBpsUpdatedProtocolReserveWithdrawnSushiFeeBpsUpdatedTokenLaunched

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "initialProtocolRecipient",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "factory_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint64",
        "name": "deadline",
        "type": "uint64"
      }
    ],
    "name": "DeadlineExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyName",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyRanges",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptySymbol",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "used",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "desired",
        "type": "uint256"
      }
    ],
    "name": "ExcessTokenConsumption",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "required",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      }
    ],
    "name": "InsufficientLaunchFee",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "InvalidFeeBps",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidPositionManager",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "quoteToken",
        "type": "address"
      }
    ],
    "name": "InvalidQuoteToken",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      },
      {
        "internalType": "int24",
        "name": "startTick",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "endTick",
        "type": "int24"
      }
    ],
    "name": "InvalidRangeOrder",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "InvalidReserveBps",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      },
      {
        "internalType": "int24",
        "name": "tick",
        "type": "int24"
      }
    ],
    "name": "InvalidTickBounds",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "int24",
        "name": "actualTickSpacing",
        "type": "int24"
      }
    ],
    "name": "InvalidTickSpacing",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      },
      {
        "internalType": "int24",
        "name": "tick",
        "type": "int24"
      }
    ],
    "name": "InvalidTickSpacingAt",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "NameTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NativeTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoCleanTokenAddress",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      },
      {
        "internalType": "int24",
        "name": "previousEndTick",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "startTick",
        "type": "int24"
      }
    ],
    "name": "NonContiguousRanges",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "NonzeroQuoteTokenUsed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NothingToWithdraw",
    "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": "OwnershipRenunciationDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "name": "PoolConfigurationMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "expected",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "actual",
        "type": "address"
      }
    ],
    "name": "PoolCreationMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint160",
        "name": "expected",
        "type": "uint160"
      },
      {
        "internalType": "uint160",
        "name": "actual",
        "type": "uint160"
      }
    ],
    "name": "PoolInitializationMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      }
    ],
    "name": "PositionConfigurationMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "RangeAmountOverflow",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "allocation",
        "type": "uint256"
      }
    ],
    "name": "RangeAmountsExceedAllocation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "ReserveAlreadyWithdrawn",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint64",
        "name": "unlockAt",
        "type": "uint64"
      }
    ],
    "name": "ReserveStillLocked",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "accounted",
        "type": "uint256"
      }
    ],
    "name": "SupplyReconciliationFailure",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "SymbolTooLong",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "maximum",
        "type": "uint256"
      }
    ],
    "name": "TooManyRanges",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      }
    ],
    "name": "UnexpectedExistingPool",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "collection",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      }
    ],
    "name": "UnexpectedNft",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "UnknownToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "ZeroLiquidity",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "ZeroRangeAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "previousBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "newBps",
        "type": "uint16"
      }
    ],
    "name": "DefaultSushiFeeBpsUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "protocolRecipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "sushiFeeBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "quoteCollected",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenCollected",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "quoteToSushi",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenToSushi",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "quoteToCreator",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenToCreator",
        "type": "uint256"
      }
    ],
    "name": "FeesDistributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "previousFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "LaunchFeeUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "LaunchFeesWithdrawn",
    "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": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "tickLower",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "tickUpper",
        "type": "int24"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenDesired",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenUsed",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      }
    ],
    "name": "PositionCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousRecipient",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newRecipient",
        "type": "address"
      }
    ],
    "name": "ProtocolRecipientUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "previousBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "newBps",
        "type": "uint16"
      }
    ],
    "name": "ProtocolReserveBpsUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ProtocolReserveWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "previousSushiFeeBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "newSushiFeeBps",
        "type": "uint16"
      }
    ],
    "name": "SushiFeeBpsUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "quoteToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "decimals",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "totalSupply",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "reserveBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "reserveAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "reserveUnlockAt",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "initialSushiFeeBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionCount",
        "type": "uint256"
      }
    ],
    "name": "TokenLaunched",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultSushiFeeBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "distributeFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "quoteCollected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenCollected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "quoteToSushi",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "tokenToSushi",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          }
        ],
        "internalType": "struct ISushiLaunchpad.TokenConfig",
        "name": "tokenConfig",
        "type": "tuple"
      },
      {
        "internalType": "address",
        "name": "quoteToken",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "startTick",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "endTick",
            "type": "int24"
          },
          {
            "internalType": "uint256",
            "name": "amount",
            "type": "uint256"
          }
        ],
        "internalType": "struct ISushiLaunchpad.SaleRange[]",
        "name": "ranges",
        "type": "tuple[]"
      },
      {
        "internalType": "uint64",
        "name": "deadline",
        "type": "uint64"
      }
    ],
    "name": "launch",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "uint256[]",
        "name": "positionIds",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "launchInfo",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "quoteToken",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pool",
            "type": "address"
          },
          {
            "internalType": "uint64",
            "name": "reserveUnlockAt",
            "type": "uint64"
          },
          {
            "internalType": "bool",
            "name": "reserveWithdrawn",
            "type": "bool"
          },
          {
            "internalType": "uint256",
            "name": "reserveAmount",
            "type": "uint256"
          }
        ],
        "internalType": "struct ISushiLaunchpad.LaunchInfo",
        "name": "info",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "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": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolReserveBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "newSushiFeeBps",
        "type": "uint16"
      }
    ],
    "name": "setDefaultSushiFeeBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "newLaunchFee",
        "type": "uint256"
      }
    ],
    "name": "setLaunchFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newRecipient",
        "type": "address"
      }
    ],
    "name": "setProtocolRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "newReserveBps",
        "type": "uint16"
      }
    ],
    "name": "setProtocolReserveBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "newSushiFeeBps",
        "type": "uint16"
      }
    ],
    "name": "setSushiFeeBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "sushiFeeBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v3Factory",
    "outputs": [
      {
        "internalType": "contract ISushiV3Factory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawLaunchFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "withdrawProtocolReserve",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

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 {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import {ISushiLaunchpad} from "./interfaces/ISushiLaunchpad.sol";
import {
    INonfungiblePositionManager,
    ISushiV3Factory,
    ISushiV3Pool
} from "./interfaces/ISushiV3.sol";
import {TickMath} from "./libraries/TickMath.sol";
import {
    TOKEN_DECIMALS,
    TOKEN_TOTAL_SUPPLY
} from "./libraries/TokenConstants.sol";
import {SushiLaunchpadToken} from "./SushiLaunchpadToken.sol";

/// @title SushiLaunchpad
/// @notice Creates fixed-supply tokens and permanently custodies their SushiSwap V3 positions.
contract SushiLaunchpad is
    ISushiLaunchpad,
    Ownable2Step,
    ReentrancyGuard,
    IERC721Receiver
{
    using SafeERC20 for IERC20;

    /// @dev Values snapshotted before collecting a token's positions so one
    /// distribution cannot mix recipients or fee splits.
    struct FeeDistributionCache {
        address creator;
        address sushiRecipient;
        address pool;
        address quoteToken;
        uint16 bps;
        uint256 quoteToCreator;
        uint256 tokenToCreator;
    }

    /// @dev Working state for an atomic launch. Mutable protocol settings are
    /// copied here before any external V3 calls are made.
    struct LaunchCache {
        address token;
        address quoteToken;
        address pool;
        bool tokenIs0;
        uint16 reserveBps;
        uint16 sushiFeeBps;
        uint256 reserveAmount;
        uint256 liquidityAllocation;
        uint256 tokenUsed;
    }

    /// @dev Canonical token ordering and deadline shared by every position in
    /// one launch.
    struct MintContext {
        address token;
        address pool;
        address token0;
        address token1;
        bool tokenIs0;
        uint64 deadline;
    }

    /// @dev SushiSwap V3's 1% fee tier, expressed in hundredths of a basis point.
    uint24 internal constant POOL_FEE = 10_000;

    /// @dev Required tick spacing for the 1% SushiSwap V3 fee tier.
    int24 internal constant TICK_SPACING = 200;

    /// @dev Lowest valid tick aligned to the configured tick spacing.
    int24 internal constant MIN_USABLE_TICK = -887_200;

    /// @dev Highest valid tick aligned to the configured tick spacing.
    int24 internal constant MAX_USABLE_TICK = 887_200;

    /// @dev Denominator used for reserve and trading-fee basis points.
    uint16 internal constant BPS_DENOMINATOR = 10_000;

    /// @dev Maximum protocol reserve for future launches: 10% of supply.
    uint16 internal constant MAX_PROTOCOL_RESERVE_BPS = 1_000;

    /// @dev Time each launch's protocol reserve remains locked.
    uint256 internal constant RESERVE_LOCK_DURATION = 365 days;

    /// @dev Maximum number of V3 NFT positions created by one launch.
    uint256 internal constant MAX_RANGES = 16;

    /// @dev Maximum CREATE2 token addresses checked when avoiding poisoned pools.
    uint256 internal constant MAX_ADDRESS_ATTEMPTS = 8;

    /// @dev Maximum UTF-8 byte length accepted for a token name.
    uint256 internal constant MAX_NAME_LENGTH = 64;

    /// @dev Maximum UTF-8 byte length accepted for a token symbol.
    uint256 internal constant MAX_SYMBOL_LENGTH = 16;

    /// @notice SushiSwap V3 factory used to create and validate launch pools.
    ISushiV3Factory public immutable v3Factory;

    /// @notice Position manager used to mint launch NFTs owned by this contract.
    INonfungiblePositionManager public immutable positionManager;

    /// @notice Current recipient of Sushi-owned fees and unlocked reserves.
    address public override protocolRecipient;

    /// @notice Reserve share copied into each future launch; initially 3%.
    uint16 public override protocolReserveBps = 300;

    /// @notice Sushi fee share copied into each future launch; initially 70%.
    uint16 public override defaultSushiFeeBps = 7_000;

    /// @notice Minimum native currency payment required to launch a token.
    uint256 public override launchFee = 0.0005 ether;

    /// @dev Number of completed launches, mixed into CREATE2 salts. Historical
    /// launch counts are indexed from TokenLaunched events.
    uint256 private _launchNonce;

    // Minimal state needed by live protocol behavior. Historical discovery and
    // attribution are reconstructed from canonical events.
    mapping(address token => LaunchInfo info) private _launches;
    mapping(address token => uint16 bps) private _tokenSushiFeeBps;
    mapping(address token => uint256[] positionIds) private _positions;

    /// @dev Restricts ERC-721 callbacks to NFTs minted during the active V3 call.
    bool private _acceptingPositionNft;

    /// @param initialOwner Initial two-step protocol owner.
    /// @param initialProtocolRecipient Initial recipient of Sushi-owned assets.
    /// @param factory_ SushiSwap V3 factory for the deployment chain.
    /// @param positionManager_ SushiSwap V3 non-fungible position manager.
    constructor(
        address initialOwner,
        address initialProtocolRecipient,
        address factory_,
        address positionManager_
    ) Ownable(initialOwner) {
        if (
            initialOwner == address(0) ||
            initialProtocolRecipient == address(0) ||
            factory_ == address(0) ||
            positionManager_ == address(0)
        ) revert ZeroAddress();

        address managerFactory;
        try INonfungiblePositionManager(positionManager_).factory() returns (
            address value
        ) {
            managerFactory = value;
        } catch {
            revert InvalidPositionManager();
        }
        if (managerFactory != factory_) revert InvalidPositionManager();

        int24 actualTickSpacing;
        try ISushiV3Factory(factory_).feeAmountTickSpacing(POOL_FEE) returns (
            int24 value
        ) {
            actualTickSpacing = value;
        } catch {
            revert InvalidTickSpacing(0);
        }
        if (actualTickSpacing != TICK_SPACING)
            revert InvalidTickSpacing(actualTickSpacing);

        protocolRecipient = initialProtocolRecipient;
        v3Factory = ISushiV3Factory(factory_);
        positionManager = INonfungiblePositionManager(positionManager_);
    }

    function launch(
        TokenConfig calldata tokenConfig,
        address quoteToken,
        SaleRange[] calldata ranges,
        uint64 deadline
    )
        external
        payable
        override
        nonReentrant
        returns (address token, address pool, uint256[] memory positionIds)
    {
        LaunchCache memory cache;
        cache.quoteToken = quoteToken;
        cache.reserveBps = protocolReserveBps;
        cache.sushiFeeBps = defaultSushiFeeBps;
        (cache.reserveAmount, cache.liquidityAllocation) = _validateLaunch(
            tokenConfig,
            cache.quoteToken,
            ranges,
            deadline,
            cache.reserveBps
        );

        cache.token = _deployCleanToken(
            tokenConfig,
            cache.quoteToken,
            ranges,
            deadline
        );
        cache.tokenIs0 = cache.token < cache.quoteToken;
        cache.pool = _createAndInitializePool(
            cache.token,
            cache.quoteToken,
            cache.tokenIs0,
            ranges[0].startTick
        );

        IERC20 launchToken = IERC20(cache.token);
        launchToken.forceApprove(
            address(positionManager),
            cache.liquidityAllocation
        );

        (positionIds, cache.tokenUsed) = _mintPositions(
            cache.token,
            cache.quoteToken,
            cache.pool,
            cache.tokenIs0,
            ranges,
            cache.liquidityAllocation,
            deadline
        );

        launchToken.forceApprove(address(positionManager), 0);
        uint256 expectedBalance = TOKEN_TOTAL_SUPPLY - cache.tokenUsed;
        uint256 actualBalance = launchToken.balanceOf(address(this));
        if (actualBalance != expectedBalance) {
            revert SupplyReconciliationFailure(expectedBalance, actualBalance);
        }

        _registerLaunch(
            cache.token,
            cache.quoteToken,
            cache.pool,
            cache.reserveAmount,
            positionIds.length,
            cache.sushiFeeBps
        );

        _emitTokenLaunched(
            cache.token,
            cache.pool,
            tokenConfig,
            positionIds.length,
            cache.reserveBps
        );
        return (cache.token, cache.pool, positionIds);
    }

    function distributeFees(
        address token
    )
        external
        override
        nonReentrant
        returns (
            uint256 quoteCollected,
            uint256 tokenCollected,
            uint256 quoteToSushi,
            uint256 tokenToSushi
        )
    {
        _requireToken(token);
        uint256[] storage positionIds = _positions[token];
        FeeDistributionCache memory cache;
        cache.creator = _launches[token].creator;
        cache.sushiRecipient = protocolRecipient;
        cache.pool = _launches[token].pool;
        cache.quoteToken = _launches[token].quoteToken;
        cache.bps = _tokenSushiFeeBps[token];

        for (uint256 i; i < positionIds.length; ++i) {
            (uint256 amount0, uint256 amount1) = positionManager.collect(
                INonfungiblePositionManager.CollectParams({
                    tokenId: positionIds[i],
                    recipient: address(this),
                    amount0Max: type(uint128).max,
                    amount1Max: type(uint128).max
                })
            );
            if (token < cache.quoteToken) {
                tokenCollected += amount0;
                quoteCollected += amount1;
            } else {
                quoteCollected += amount0;
                tokenCollected += amount1;
            }
        }

        quoteToSushi = Math.mulDiv(quoteCollected, cache.bps, BPS_DENOMINATOR);
        tokenToSushi = Math.mulDiv(tokenCollected, cache.bps, BPS_DENOMINATOR);
        cache.quoteToCreator = quoteCollected - quoteToSushi;
        cache.tokenToCreator = tokenCollected - tokenToSushi;

        _transferIfNonzero(
            IERC20(cache.quoteToken),
            cache.sushiRecipient,
            quoteToSushi
        );
        _transferIfNonzero(IERC20(token), cache.sushiRecipient, tokenToSushi);
        _transferIfNonzero(
            IERC20(cache.quoteToken),
            cache.creator,
            cache.quoteToCreator
        );
        _transferIfNonzero(IERC20(token), cache.creator, cache.tokenToCreator);

        emit FeesDistributed(
            msg.sender,
            token,
            cache.pool,
            cache.creator,
            cache.sushiRecipient,
            cache.bps,
            quoteCollected,
            tokenCollected,
            quoteToSushi,
            tokenToSushi,
            cache.quoteToCreator,
            cache.tokenToCreator
        );
    }

    function setProtocolRecipient(
        address newRecipient
    ) external override onlyOwner {
        if (newRecipient == address(0)) revert ZeroAddress();
        address previousRecipient = protocolRecipient;
        protocolRecipient = newRecipient;
        emit ProtocolRecipientUpdated(previousRecipient, newRecipient);
    }

    function setProtocolReserveBps(
        uint16 newReserveBps
    ) external override onlyOwner {
        if (newReserveBps > MAX_PROTOCOL_RESERVE_BPS)
            revert InvalidReserveBps(newReserveBps);
        uint16 previousBps = protocolReserveBps;
        protocolReserveBps = newReserveBps;
        emit ProtocolReserveBpsUpdated(previousBps, newReserveBps);
    }

    function setDefaultSushiFeeBps(
        uint16 newSushiFeeBps
    ) external override onlyOwner {
        _validateFeeBps(newSushiFeeBps);
        uint16 previousBps = defaultSushiFeeBps;
        defaultSushiFeeBps = newSushiFeeBps;
        emit DefaultSushiFeeBpsUpdated(previousBps, newSushiFeeBps);
    }

    function setLaunchFee(uint256 newLaunchFee) external override onlyOwner {
        uint256 previousFee = launchFee;
        launchFee = newLaunchFee;
        emit LaunchFeeUpdated(previousFee, newLaunchFee);
    }

    function setSushiFeeBps(
        address token,
        uint16 newSushiFeeBps
    ) external override onlyOwner {
        _requireToken(token);
        _validateFeeBps(newSushiFeeBps);
        uint16 previousBps = _tokenSushiFeeBps[token];
        _tokenSushiFeeBps[token] = newSushiFeeBps;
        emit SushiFeeBpsUpdated(token, previousBps, newSushiFeeBps);
    }

    function withdrawProtocolReserve(
        address token
    ) external override onlyOwner nonReentrant {
        _requireToken(token);
        LaunchInfo storage info = _launches[token];
        if (info.reserveWithdrawn) revert ReserveAlreadyWithdrawn(token);
        if (block.timestamp < info.reserveUnlockAt)
            revert ReserveStillLocked(info.reserveUnlockAt);

        info.reserveWithdrawn = true;
        address recipient = protocolRecipient;
        uint256 amount = info.reserveAmount;
        _transferIfNonzero(IERC20(token), recipient, amount);
        emit ProtocolReserveWithdrawn(token, recipient, amount);
    }

    function withdrawLaunchFees() external override onlyOwner nonReentrant {
        uint256 amount = address(this).balance;
        if (amount == 0) revert NothingToWithdraw();
        address recipient = protocolRecipient;
        (bool success, ) = payable(recipient).call{value: amount}("");
        if (!success) revert NativeTransferFailed();
        emit LaunchFeesWithdrawn(recipient, amount);
    }

    function renounceOwnership() public view override onlyOwner {
        revert OwnershipRenunciationDisabled();
    }

    function launchInfo(
        address token
    ) external view override returns (LaunchInfo memory info) {
        _requireToken(token);
        return _launches[token];
    }

    function sushiFeeBps(
        address token
    ) external view override returns (uint16) {
        _requireToken(token);
        return _tokenSushiFeeBps[token];
    }

    function onERC721Received(
        address operator,
        address from,
        uint256,
        bytes calldata
    ) external view override returns (bytes4) {
        if (
            msg.sender != address(positionManager) ||
            !_acceptingPositionNft ||
            operator != address(this) ||
            from != address(0)
        ) revert UnexpectedNft(msg.sender, operator, from);
        return IERC721Receiver.onERC721Received.selector;
    }

    function _validateLaunch(
        TokenConfig calldata tokenConfig,
        address quoteToken,
        SaleRange[] calldata ranges,
        uint64 deadline,
        uint16 reserveBps
    )
        private
        view
        returns (uint256 reserveAmount, uint256 liquidityAllocation)
    {
        if (msg.value < launchFee)
            revert InsufficientLaunchFee(launchFee, msg.value);
        if (block.timestamp > deadline) revert DeadlineExpired(deadline);
        if (quoteToken == address(0)) revert ZeroAddress();
        if (quoteToken.code.length == 0) revert InvalidQuoteToken(quoteToken);
        if (bytes(tokenConfig.name).length == 0) revert EmptyName();
        if (bytes(tokenConfig.symbol).length == 0) revert EmptySymbol();
        if (bytes(tokenConfig.name).length > MAX_NAME_LENGTH)
            revert NameTooLong(bytes(tokenConfig.name).length);
        if (bytes(tokenConfig.symbol).length > MAX_SYMBOL_LENGTH)
            revert SymbolTooLong(bytes(tokenConfig.symbol).length);

        uint256 length = ranges.length;
        if (length == 0) revert EmptyRanges();
        if (length > MAX_RANGES) revert TooManyRanges(length, MAX_RANGES);

        reserveAmount = Math.mulDiv(
            TOKEN_TOTAL_SUPPLY,
            reserveBps,
            BPS_DENOMINATOR
        );
        liquidityAllocation = TOKEN_TOTAL_SUPPLY - reserveAmount;
        uint256 amountSum;

        for (uint256 i; i < length; ++i) {
            SaleRange calldata range = ranges[i];
            if (range.amount == 0) revert ZeroRangeAmount(i);
            if (range.startTick >= range.endTick)
                revert InvalidRangeOrder(i, range.startTick, range.endTick);
            _validateTick(i, range.startTick);
            _validateTick(i, range.endTick);
            if (i != 0 && range.startTick != ranges[i - 1].endTick) {
                revert NonContiguousRanges(
                    i,
                    ranges[i - 1].endTick,
                    range.startTick
                );
            }
            unchecked {
                uint256 newSum = amountSum + range.amount;
                if (newSum < amountSum) revert RangeAmountOverflow(i);
                amountSum = newSum;
            }
        }
        if (amountSum > liquidityAllocation) {
            revert RangeAmountsExceedAllocation(amountSum, liquidityAllocation);
        }
    }

    function _validateTick(uint256 index, int24 tick) private pure {
        if (tick < MIN_USABLE_TICK || tick > MAX_USABLE_TICK)
            revert InvalidTickBounds(index, tick);
        if (tick % TICK_SPACING != 0) revert InvalidTickSpacingAt(index, tick);
    }

    function _deployCleanToken(
        TokenConfig calldata tokenConfig,
        address quoteToken,
        SaleRange[] calldata ranges,
        uint64 deadline
    ) private returns (address token) {
        bytes32 initCodeHash = keccak256(
            abi.encodePacked(
                type(SushiLaunchpadToken).creationCode,
                abi.encode(tokenConfig.name, tokenConfig.symbol, address(this))
            )
        );
        bytes32 parameterHash = keccak256(
            abi.encode(tokenConfig, quoteToken, ranges, deadline)
        );
        uint256 nonce = _launchNonce;

        for (uint256 i; i < MAX_ADDRESS_ATTEMPTS; ++i) {
            bytes32 salt = keccak256(
                abi.encode(
                    msg.sender,
                    nonce,
                    parameterHash,
                    block.prevrandao,
                    block.number,
                    i
                )
            );
            address candidate = _computeCreate2Address(salt, initCodeHash);
            if (
                candidate.code.length != 0 ||
                v3Factory.getPool(candidate, quoteToken, POOL_FEE) != address(0)
            ) continue;

            token = _deployToken(salt, tokenConfig);
            break;
        }
        if (token == address(0)) revert NoCleanTokenAddress();

        address existingPool = v3Factory.getPool(token, quoteToken, POOL_FEE);
        if (existingPool != address(0))
            revert UnexpectedExistingPool(token, existingPool);
    }

    function _deployToken(
        bytes32 salt,
        TokenConfig calldata tokenConfig
    ) private returns (address) {
        return
            address(
                new SushiLaunchpadToken{salt: salt}(
                    tokenConfig.name,
                    tokenConfig.symbol,
                    address(this)
                )
            );
    }

    function _createAndInitializePool(
        address token,
        address quoteToken,
        bool tokenIs0,
        int24 firstStartTick
    ) private returns (address pool) {
        pool = v3Factory.createPool(token, quoteToken, POOL_FEE);
        address registeredPool = v3Factory.getPool(token, quoteToken, POOL_FEE);
        if (pool == address(0) || registeredPool != pool)
            revert PoolCreationMismatch(pool, registeredPool);

        ISushiV3Pool sushiPool = ISushiV3Pool(pool);
        address token0 = tokenIs0 ? token : quoteToken;
        address token1 = tokenIs0 ? quoteToken : token;
        if (
            sushiPool.token0() != token0 ||
            sushiPool.token1() != token1 ||
            sushiPool.fee() != POOL_FEE ||
            sushiPool.tickSpacing() != TICK_SPACING
        ) revert PoolConfigurationMismatch(pool);

        int24 initialTick = tokenIs0 ? firstStartTick : -firstStartTick;
        uint160 expectedSqrtPriceX96 = TickMath.getSqrtRatioAtTick(initialTick);
        sushiPool.initialize(expectedSqrtPriceX96);
        (uint160 actualSqrtPriceX96, , , , , , ) = sushiPool.slot0();
        if (actualSqrtPriceX96 != expectedSqrtPriceX96) {
            revert PoolInitializationMismatch(
                expectedSqrtPriceX96,
                actualSqrtPriceX96
            );
        }
    }

    function _mintPositions(
        address token,
        address quoteToken,
        address pool,
        bool tokenIs0,
        SaleRange[] calldata ranges,
        uint256 liquidityAllocation,
        uint64 deadline
    ) private returns (uint256[] memory positionIds, uint256 totalTokenUsed) {
        uint256 amountSum;
        for (uint256 i; i < ranges.length; ++i) amountSum += ranges[i].amount;
        uint256 firstRangeRemainder = liquidityAllocation - amountSum;

        MintContext memory context = MintContext({
            token: token,
            pool: pool,
            token0: tokenIs0 ? token : quoteToken,
            token1: tokenIs0 ? quoteToken : token,
            tokenIs0: tokenIs0,
            deadline: deadline
        });
        positionIds = new uint256[](ranges.length);

        for (uint256 i; i < ranges.length; ++i) {
            uint256 tokenDesired =
                ranges[i].amount + (i == 0 ? firstRangeRemainder : 0);
            (uint256 positionId, uint256 tokenUsed) = _mintOnePosition(
                context,
                ranges[i],
                tokenDesired,
                i
            );
            positionIds[i] = positionId;
            totalTokenUsed += tokenUsed;
        }
    }

    function _mintOnePosition(
        MintContext memory context,
        SaleRange calldata range,
        uint256 tokenDesired,
        uint256 index
    ) private returns (uint256 positionId, uint256 tokenUsed) {
        (int24 tickLower, int24 tickUpper) = _canonicalTicks(
            range,
            context.tokenIs0
        );
        uint128 liquidity;
        uint256 amount0;
        uint256 amount1;
        (positionId, liquidity, amount0, amount1) = _mintPosition(
            context.token0,
            context.token1,
            tickLower,
            tickUpper,
            tokenDesired,
            context.tokenIs0,
            context.deadline
        );
        tokenUsed = context.tokenIs0 ? amount0 : amount1;
        uint256 quoteTokenUsed = context.tokenIs0 ? amount1 : amount0;
        if (liquidity == 0) revert ZeroLiquidity(index);
        if (quoteTokenUsed != 0)
            revert NonzeroQuoteTokenUsed(index, quoteTokenUsed);
        if (tokenUsed > tokenDesired)
            revert ExcessTokenConsumption(index, tokenUsed, tokenDesired);

        _validatePosition(
            positionId,
            context.token0,
            context.token1,
            tickLower,
            tickUpper,
            liquidity
        );
        _positions[context.token].push(positionId);
        emit PositionCreated(
            context.token,
            context.pool,
            positionId,
            tickLower,
            tickUpper,
            tokenDesired,
            tokenUsed,
            liquidity
        );
    }

    function _mintPosition(
        address token0,
        address token1,
        int24 tickLower,
        int24 tickUpper,
        uint256 tokenDesired,
        bool tokenIs0,
        uint64 deadline
    )
        private
        returns (
            uint256 positionId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        _acceptingPositionNft = true;
        (positionId, liquidity, amount0, amount1) = positionManager.mint(
            INonfungiblePositionManager.MintParams({
                token0: token0,
                token1: token1,
                fee: POOL_FEE,
                tickLower: tickLower,
                tickUpper: tickUpper,
                amount0Desired: tokenIs0 ? tokenDesired : 0,
                amount1Desired: tokenIs0 ? 0 : tokenDesired,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: deadline
            })
        );
        _acceptingPositionNft = false;
    }

    function _validatePosition(
        uint256 positionId,
        address expectedToken0,
        address expectedToken1,
        int24 expectedTickLower,
        int24 expectedTickUpper,
        uint128 expectedLiquidity
    ) private view {
        (
            ,
            ,
            address actualToken0,
            address actualToken1,
            uint24 actualFee,
            int24 actualTickLower,
            int24 actualTickUpper,
            uint128 actualLiquidity,
            ,
            ,
            ,

        ) = positionManager.positions(positionId);
        if (
            positionManager.ownerOf(positionId) != address(this) ||
            actualToken0 != expectedToken0 ||
            actualToken1 != expectedToken1 ||
            actualFee != POOL_FEE ||
            actualTickLower != expectedTickLower ||
            actualTickUpper != expectedTickUpper ||
            actualLiquidity != expectedLiquidity
        ) revert PositionConfigurationMismatch(positionId);
    }

    function _registerLaunch(
        address token,
        address quoteToken,
        address pool,
        uint256 reserveAmount,
        uint256 positionIdsLength,
        uint16 initialSushiFeeBps
    ) private {
        uint64 reserveUnlockAt =
            uint64(block.timestamp) + uint64(RESERVE_LOCK_DURATION);
        _launches[token] = LaunchInfo({
            creator: msg.sender,
            quoteToken: quoteToken,
            pool: pool,
            reserveUnlockAt: reserveUnlockAt,
            reserveWithdrawn: false,
            reserveAmount: reserveAmount
        });
        _tokenSushiFeeBps[token] = initialSushiFeeBps;
        ++_launchNonce;

        assert(_positions[token].length == positionIdsLength);
    }

    function _emitTokenLaunched(
        address token,
        address pool,
        TokenConfig calldata tokenConfig,
        uint256 positionsLength,
        uint16 reserveBps
    ) private {
        LaunchInfo storage info = _launches[token];
        emit TokenLaunched(
            msg.sender,
            token,
            pool,
            info.quoteToken,
            tokenConfig.name,
            tokenConfig.symbol,
            TOKEN_DECIMALS,
            TOKEN_TOTAL_SUPPLY,
            reserveBps,
            info.reserveAmount,
            info.reserveUnlockAt,
            _tokenSushiFeeBps[token],
            positionsLength
        );
    }

    function _canonicalTicks(
        SaleRange calldata range,
        bool tokenIs0
    ) private pure returns (int24 lower, int24 upper) {
        if (tokenIs0) return (range.startTick, range.endTick);
        return (-range.endTick, -range.startTick);
    }

    function _computeCreate2Address(
        bytes32 salt,
        bytes32 initCodeHash
    ) private view returns (address) {
        return
            address(
                uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                bytes1(0xff),
                                address(this),
                                salt,
                                initCodeHash
                            )
                        )
                    )
                )
            );
    }

    function _requireToken(address token) private view {
        if (_launches[token].pool == address(0)) revert UnknownToken(token);
    }

    function _validateFeeBps(uint16 bps) private pure {
        if (bps > BPS_DENOMINATOR) revert InvalidFeeBps(bps);
    }

    function _transferIfNonzero(
        IERC20 token,
        address recipient,
        uint256 amount
    ) private {
        if (amount != 0) token.safeTransfer(recipient, amount);
    }
}
Chain explorer5848msChain node78ms