SteakHouseV4Factory
0x97bec1a8b3ed7caac59f687a51f898b75b774e83
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
21,612 bytes
ABI entries
89
32 read · 5 write
License
none
Contract information
- Address
- 0x97bec1a8b3ed7caac59f687a51f898b75b774e83
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x7B7DE29bc1…C6b0DaB694
- Creation tx
- 0x3235e0cbe1…000cf3eeb8
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (32)
BPS() → uint256
MAX_LP_FEE() → uint24
MIN_LP_FEE() → uint24
MODE_BAND() → uint8
MODE_TIME() → uint8
PERMIT2() → address
SNIPER_MAX_BPS() → uint24
SNIPER_MIN_BPS() → uint24
STARTING_MCAP() → uint256
TICK_LOWER() → int24
TICK_UPPER() → int24
TIME_MAX_FEE_START() → uint24
TIME_MAX_INTERVAL() → uint256
TIME_MIN_INTERVAL() → uint256
TOTAL_SUPPLY() → uint256
UNBRANDING_FEE() → uint256
VANITY_FEE() → uint256
antiPvpRegistry() → address
config() → address
failedFeeETH(bytes32) → uint256
hook() → address
launchByPoolId(bytes32) → tuple
launchByToken(address) → tuple
owner() → address
poolActivated(bytes32) → bool
poolManager() → address
poolStates(bytes32) → address, address, uint256, uint256, bool
positionManager() → address
positionTokenId(bytes32) → uint256
previewSqrtPrice(int24) → uint160
tokenIdToPoolId(uint256) → bytes32
totalLaunches() → uint256
Events (12)
FailedFeesRecoveredFeeSendFailedFeeSplitDistributedFeesCollectedFromPositionLaunchFeePaidLiquidityAddedLiquidityRepositionedOwnershipTransferredPoolActivatedPositionMintedPositionRetiredTokenLaunched
ABI
[
{
"inputs": [
{
"internalType": "contract SteakHouseV4Config",
"name": "_config",
"type": "address"
},
{
"internalType": "contract IPoolManager",
"name": "_poolManager",
"type": "address"
},
{
"internalType": "contract SteakHouseV4Hook",
"name": "_hook",
"type": "address"
},
{
"internalType": "address",
"name": "_positionManager",
"type": "address"
},
{
"internalType": "contract AntiPVPRegistry",
"name": "_antiPvpRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AgentOnly",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyMinted",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyRetired",
"type": "error"
},
{
"inputs": [],
"name": "AntiPVPDuplicateCombo",
"type": "error"
},
{
"inputs": [],
"name": "EmptyName",
"type": "error"
},
{
"inputs": [],
"name": "EmptySymbol",
"type": "error"
},
{
"inputs": [],
"name": "FeeTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "FirstBuyWindowActive",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientBootstrap",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientFees",
"type": "error"
},
{
"inputs": [],
"name": "InvalidFeeSplit",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParams",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPosition",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSalt",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSniperTax",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTargetMcap",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTimeConfig",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTokenId",
"type": "error"
},
{
"inputs": [],
"name": "LaunchNotFound",
"type": "error"
},
{
"inputs": [],
"name": "LiquidityAddFailed",
"type": "error"
},
{
"inputs": [],
"name": "NameTooLong",
"type": "error"
},
{
"inputs": [],
"name": "NotAuthorized",
"type": "error"
},
{
"inputs": [],
"name": "NotMinted",
"type": "error"
},
{
"inputs": [],
"name": "OnlyCreatorOrOwner",
"type": "error"
},
{
"inputs": [],
"name": "OnlyHook",
"type": "error"
},
{
"inputs": [],
"name": "OnlyPoolManager",
"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": "PoolIdNotFound",
"type": "error"
},
{
"inputs": [],
"name": "ProtocolPaused",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "SqrtPriceOverflow",
"type": "error"
},
{
"inputs": [],
"name": "SymbolTooLong",
"type": "error"
},
{
"inputs": [],
"name": "TokenApprovalFailed",
"type": "error"
},
{
"inputs": [],
"name": "TransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddressInSplit",
"type": "error"
},
{
"inputs": [],
"name": "ZeroBeneficiary",
"type": "error"
},
{
"inputs": [],
"name": "ZeroLiquidity",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "FailedFeesRecovered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "FeeSendFailed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "wallet",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "FeeSplitDistributed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint256",
"name": "positionIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "FeesCollectedFromPosition",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "LaunchFeePaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "liquidityMinted",
"type": "uint256"
}
],
"name": "LiquidityAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "oldTokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newTokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "LiquidityRepositioned",
"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": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bool",
"name": "isDevActivation",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "liquidityMinted",
"type": "uint256"
}
],
"name": "PoolActivated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint256",
"name": "positionIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "PositionMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint256",
"name": "positionIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethRecovered",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensRecovered",
"type": "uint256"
}
],
"name": "PositionRetired",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
}
],
"name": "TokenLaunched",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_LP_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_LP_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MODE_BAND",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MODE_TIME",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PERMIT2",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SNIPER_MAX_BPS",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SNIPER_MIN_BPS",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "STARTING_MCAP",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_LOWER",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_UPPER",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TIME_MAX_FEE_START",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TIME_MAX_INTERVAL",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TIME_MIN_INTERVAL",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOTAL_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "UNBRANDING_FEE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VANITY_FEE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "antiPvpRegistry",
"outputs": [
{
"internalType": "contract AntiPVPRegistry",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "positionIndex",
"type": "uint256"
}
],
"name": "collectFeesFromPosition",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "config",
"outputs": [
{
"internalType": "contract SteakHouseV4Config",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"components": [
{
"internalType": "address[5]",
"name": "wallets",
"type": "address[5]"
},
{
"internalType": "uint16[5]",
"name": "percentages",
"type": "uint16[5]"
},
{
"internalType": "uint8",
"name": "count",
"type": "uint8"
}
],
"internalType": "struct SteakHouseV4Factory.FeeSplit",
"name": "feeSplit",
"type": "tuple"
},
{
"internalType": "uint24[8]",
"name": "lpFeesPerBand",
"type": "uint24[8]"
},
{
"internalType": "uint256[8]",
"name": "maxWalletBps",
"type": "uint256[8]"
},
{
"internalType": "uint256[8]",
"name": "maxTxBps",
"type": "uint256[8]"
},
{
"internalType": "bool",
"name": "branded",
"type": "bool"
},
{
"internalType": "bool",
"name": "vanity",
"type": "bool"
},
{
"internalType": "bytes32",
"name": "customSalt",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "mode",
"type": "uint8"
},
{
"components": [
{
"internalType": "uint64",
"name": "maxTxInterval",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "maxWalletInterval",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "feeDropInterval",
"type": "uint64"
},
{
"internalType": "uint24",
"name": "feeStart",
"type": "uint24"
},
{
"internalType": "uint24",
"name": "feeFloor",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "maxTxStart",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxTxStep",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxWalletStart",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxWalletStep",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeDropStep",
"type": "uint256"
}
],
"internalType": "struct TimeLimits.TimeConfig",
"name": "timeConfig",
"type": "tuple"
},
{
"internalType": "bool",
"name": "sniperTaxEnabled",
"type": "bool"
},
{
"internalType": "uint24",
"name": "sniperTaxBps",
"type": "uint24"
}
],
"internalType": "struct SteakHouseV4Factory.CreateParams",
"name": "params",
"type": "tuple"
}
],
"name": "createLaunch",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "",
"type": "bytes32"
}
],
"name": "failedFeeETH",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "hook",
"outputs": [
{
"internalType": "contract SteakHouseV4Hook",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "launchByPoolId",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "poolKey",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "createdBlock",
"type": "uint256"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"components": [
{
"internalType": "address[5]",
"name": "wallets",
"type": "address[5]"
},
{
"internalType": "uint16[5]",
"name": "percentages",
"type": "uint16[5]"
},
{
"internalType": "uint8",
"name": "count",
"type": "uint8"
}
],
"internalType": "struct SteakHouseV4Factory.FeeSplit",
"name": "feeSplit",
"type": "tuple"
},
{
"internalType": "uint24[8]",
"name": "lpFeesPerBand",
"type": "uint24[8]"
},
{
"internalType": "uint256[8]",
"name": "maxWalletBps",
"type": "uint256[8]"
},
{
"internalType": "uint256[8]",
"name": "maxTxBps",
"type": "uint256[8]"
},
{
"internalType": "bool",
"name": "branded",
"type": "bool"
}
],
"internalType": "struct SteakHouseV4Factory.LaunchInfo",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "launchByToken",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "poolKey",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "createdBlock",
"type": "uint256"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"components": [
{
"internalType": "address[5]",
"name": "wallets",
"type": "address[5]"
},
{
"internalType": "uint16[5]",
"name": "percentages",
"type": "uint16[5]"
},
{
"internalType": "uint8",
"name": "count",
"type": "uint8"
}
],
"internalType": "struct SteakHouseV4Factory.FeeSplit",
"name": "feeSplit",
"type": "tuple"
},
{
"internalType": "uint24[8]",
"name": "lpFeesPerBand",
"type": "uint24[8]"
},
{
"internalType": "uint256[8]",
"name": "maxWalletBps",
"type": "uint256[8]"
},
{
"internalType": "uint256[8]",
"name": "maxTxBps",
"type": "uint256[8]"
},
{
"internalType": "bool",
"name": "branded",
"type": "bool"
}
],
"internalType": "struct SteakHouseV4Factory.LaunchInfo",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "poolActivated",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "",
"type": "bytes32"
}
],
"name": "poolStates",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
},
{
"internalType": "uint256",
"name": "totalSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "recycledETH",
"type": "uint256"
},
{
"internalType": "bool",
"name": "activated",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "positionTokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "previewSqrtPrice",
"outputs": [
{
"internalType": "uint160",
"name": "",
"type": "uint160"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "recoverFailedFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tokenIdToPoolId",
"outputs": [
{
"internalType": "PoolId",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalLaunches",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";
import {Actions} from "v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {PositionInfo, PositionInfoLibrary} from "v4-periphery/src/libraries/PositionInfoLibrary.sol";
import {SteakHouseV4Token} from "./SteakHouseV4Token.sol";
import {Token} from "./SteakHouseV4TokenSteakHouse.sol";
import {SteakHouseV4Config} from "./SteakHouseV4Config.sol";
import {SteakHouseV4Hook} from "./SteakHouseV4Hook.sol";
import {AntiPVPRegistry} from "./AntiPVPRegistry.sol";
import {PriceMath} from "../libraries/PriceMath.sol";
import {TimeLimits} from "../libraries/TimeLimits.sol";
contract SteakHouseV4Factory is Ownable, ReentrancyGuard {
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
using PositionInfoLibrary for PositionInfo;
uint256 public constant TOTAL_SUPPLY = 1_000_000_000 * 1e18;
uint256 public constant BPS = 10000;
uint256 public constant UNBRANDING_FEE = 0.001 ether;
uint256 public constant VANITY_FEE = 0.0005 ether;
/// @notice V4 fee unit: 1 = 0.0001%. LP fee per band must be 1.2%..10%
/// (12000..100000) — DIRECT V4 parity.
uint24 public constant MIN_LP_FEE = 12000;
uint24 public constant MAX_LP_FEE = 100000;
/// @notice Launch limit/fee modes.
uint8 public constant MODE_BAND = 0;
uint8 public constant MODE_TIME = 1;
/// @notice TIME-mode bounds (V2 parity + V4 fee floor). Fee in v4 units;
/// start/step limits in TOKENS validated as 0.1%..5% of supply.
uint24 public constant TIME_MAX_FEE_START = 200000; // 20%
uint256 public constant TIME_MIN_INTERVAL = 1; // seconds
uint256 public constant TIME_MAX_INTERVAL = 60; // seconds
/// @notice Anti-snipe tax bounds (v4 fee units, 1 = 0.0001%). When enabled the
/// dev picks a sniper tax in [40%, 80%] charged as the LP fee during
/// the first SNIPER_SECONDS after launch (standalone — it REPLACES the
/// normal band/time fee in that window, then normal fees resume).
uint24 public constant SNIPER_MIN_BPS = 400000; // 40%
uint24 public constant SNIPER_MAX_BPS = 800000; // 80%
address public constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
uint256 public constant STARTING_MCAP = 1 ether;
int24 public constant TICK_LOWER = -887220;
int24 public constant TICK_UPPER = 887220;
SteakHouseV4Config public immutable config;
IPoolManager public immutable poolManager;
SteakHouseV4Hook public immutable hook;
address public immutable positionManager;
AntiPVPRegistry public immutable antiPvpRegistry;
uint256 public totalLaunches;
mapping(address => LaunchInfo) internal _launchByToken;
mapping(PoolId => LaunchInfo) internal _launchByPoolId;
mapping(PoolId => PoolState) public poolStates;
mapping(uint256 => PoolId) public tokenIdToPoolId;
/// @notice ETH fee shares whose push to a recipient reverted, parked per pool
/// so a single reverting/blacklisting fee recipient cannot brick a
/// pool's entire fee collection (A1 fix). Owner-recoverable.
mapping(PoolId => uint256) public failedFeeETH;
struct PoolState {
address token;
address beneficiary;
uint256 totalSupply;
uint256[8] positionTokenIds;
bool[8] positionMinted;
uint256 recycledETH;
bool activated;
}
struct FeeSplit {
address[5] wallets;
uint16[5] percentages;
uint8 count;
}
struct LaunchInfo {
address token;
address beneficiary;
address creator;
PoolId poolId;
PoolKey poolKey;
uint256 createdAt;
uint256 createdBlock;
string name;
string symbol;
FeeSplit feeSplit;
uint24[8] lpFeesPerBand;
uint256[8] maxWalletBps;
uint256[8] maxTxBps;
bool branded;
}
struct CreateParams {
string name;
string symbol;
address beneficiary;
FeeSplit feeSplit;
uint24[8] lpFeesPerBand;
uint256[8] maxWalletBps;
uint256[8] maxTxBps;
bool branded;
bool vanity;
bytes32 customSalt;
uint8 mode; // 0 = BAND (per-band), 1 = TIME (time-based)
TimeLimits.TimeConfig timeConfig; // only used when mode == TIME
bool sniperTaxEnabled; // toggle a first-window anti-snipe tax
uint24 sniperTaxBps; // sniper tax in v4 fee units, 40%..80% when enabled
}
event TokenLaunched(
address indexed token,
address indexed beneficiary,
address indexed creator,
PoolId poolId,
uint160 sqrtPriceX96,
string name,
string symbol
);
event LaunchFeePaid(address indexed user, uint256 amount);
event LiquidityAdded(address indexed token, PoolId indexed poolId, uint256 tokenAmount, uint256 liquidityMinted);
event LiquidityRepositioned(address indexed token, PoolId indexed poolId, uint256 oldTokenId, uint256 newTokenId, int24 tickLower, int24 tickUpper);
event PoolActivated(PoolId indexed poolId, bool isDevActivation, uint256 liquidityMinted);
event PositionMinted(PoolId indexed poolId, uint256 indexed positionIndex, uint256 tokenId, uint256 tokenAmount);
event PositionRetired(PoolId indexed poolId, uint256 indexed positionIndex, uint256 tokenId, uint256 ethRecovered, uint256 tokensRecovered);
event FeesCollectedFromPosition(PoolId indexed poolId, uint256 indexed positionIndex, uint256 tokenId, uint256 ethAmount, uint256 tokenAmount);
event FeeSplitDistributed(PoolId indexed poolId, address indexed wallet, uint256 amount);
/// @notice Emitted when an ETH fee payout to a recipient fails; the amount is
/// parked in failedFeeETH[poolId] instead of reverting the whole
/// collection, so one bad recipient cannot brick fee collection.
event FeeSendFailed(PoolId indexed poolId, address indexed recipient, uint256 amount);
/// @notice Emitted when the owner recovers parked failed-fee ETH.
event FailedFeesRecovered(PoolId indexed poolId, address indexed to, uint256 amount);
error InvalidTimeConfig();
error InvalidSniperTax();
error InsufficientBootstrap();
error InsufficientFees();
error InvalidParams();
error ProtocolPaused();
error NameTooLong();
error SymbolTooLong();
error EmptyName();
error EmptySymbol();
error ZeroBeneficiary();
error FeeTransferFailed();
error InvalidTargetMcap();
error SqrtPriceOverflow();
error TokenApprovalFailed();
error LiquidityAddFailed();
error InvalidFeeSplit();
error ZeroAddressInSplit();
error OnlyPoolManager();
error InvalidPosition();
error InvalidSalt();
error AlreadyMinted();
error ZeroLiquidity();
error OnlyHook();
error NotMinted();
error AlreadyRetired();
error InvalidTokenId();
error PoolIdNotFound();
error LaunchNotFound();
error AgentOnly();
error OnlyCreatorOrOwner();
error FirstBuyWindowActive();
error NotAuthorized();
error TransferFailed();
error AntiPVPDuplicateCombo();
constructor(
SteakHouseV4Config _config,
IPoolManager _poolManager,
SteakHouseV4Hook _hook,
address _positionManager,
AntiPVPRegistry _antiPvpRegistry,
address _owner
) Ownable(_owner) {
config = _config;
poolManager = _poolManager;
hook = _hook;
positionManager = _positionManager;
antiPvpRegistry = _antiPvpRegistry;
}
receive() external payable {
if (msg.sender != address(poolManager)) revert OnlyPoolManager();
}
/// @notice Validate a TIME-mode config (V2 parity + V4 fee floor). maxTx /
/// maxWallet START values are TOKEN amounts, bounded 0.1%..5% of
/// supply; intervals 1..60s; fee start <= 20% and floor in
/// [MIN_LP_FEE, feeStart].
function _validateTimeConfig(TimeLimits.TimeConfig calldata t) internal pure {
uint256 minStart = TOTAL_SUPPLY / 1000; // 0.1%
uint256 maxStart = (TOTAL_SUPPLY * 5) / 100; // 5%
if (t.maxTxStart < minStart || t.maxTxStart > maxStart) revert InvalidTimeConfig();
if (t.maxWalletStart < minStart || t.maxWalletStart > maxStart) revert InvalidTimeConfig();
if (t.maxTxInterval < TIME_MIN_INTERVAL || t.maxTxInterval > TIME_MAX_INTERVAL) revert InvalidTimeConfig();
if (t.maxWalletInterval < TIME_MIN_INTERVAL || t.maxWalletInterval > TIME_MAX_INTERVAL) revert InvalidTimeConfig();
if (t.feeDropInterval < TIME_MIN_INTERVAL || t.feeDropInterval > TIME_MAX_INTERVAL) revert InvalidTimeConfig();
if (t.feeStart > TIME_MAX_FEE_START) revert InvalidTimeConfig();
if (t.feeFloor < MIN_LP_FEE || t.feeFloor > t.feeStart) revert InvalidTimeConfig();
}
function createLaunch(CreateParams calldata params)
external
payable
nonReentrant
returns (address token, PoolId poolId)
{
if (config.paused()) revert ProtocolPaused();
if (bytes(params.name).length > 32) revert NameTooLong();
if (bytes(params.name).length == 0) revert EmptyName();
if (bytes(params.symbol).length > 10) revert SymbolTooLong();
if (bytes(params.symbol).length == 0) revert EmptySymbol();
if (!antiPvpRegistry.isComboAvailable(params.name, params.symbol)) {
revert AntiPVPDuplicateCombo();
}
if (params.beneficiary == address(0)) revert ZeroBeneficiary();
// Calculate total fees: unbranding (0.0005) + vanity (0.0005)
uint256 totalFees = 0;
if (!params.branded) totalFees += UNBRANDING_FEE;
if (params.vanity) totalFees += VANITY_FEE;
// Carve out fees and validate bootstrap amount
uint256 ethForBootstrap = msg.value;
if (totalFees > 0) {
if (msg.value < totalFees) revert InsufficientFees();
ethForBootstrap = msg.value - totalFees;
(bool ok,) = config.treasury().call{value: totalFees}("");
if (!ok) revert TransferFailed();
}
uint256 bootstrapAmount = config.MIN_BOOTSTRAP_ETH();
if (ethForBootstrap < bootstrapAmount) revert InsufficientBootstrap();
bootstrapAmount = ethForBootstrap;
if (params.mode == MODE_TIME) {
// TIME mode: validate the time-based curve config (bands unused).
_validateTimeConfig(params.timeConfig);
} else {
// BAND mode: validate per-band configuration. LP fee floor is the flat
// MIN_LP_FEE (1.2%); the CEILING is per-band from the PriceMath table
// (band0 20%, band1 15%, band2 10%, bands3-7 5%) so lower-MCAP bands
// may charge more and higher bands are capped tighter.
for (uint8 i = 0; i < 8; i++) {
if (params.lpFeesPerBand[i] < MIN_LP_FEE) revert InvalidParams();
if (params.lpFeesPerBand[i] > PriceMath.getMaxLpFeeForBand(i)) revert InvalidParams();
if (params.maxWalletBps[i] < PriceMath.getMinLimitForBand(i) ||
params.maxWalletBps[i] > PriceMath.getMaxLimitForBand(i)) revert InvalidParams();
if (params.maxTxBps[i] < PriceMath.getMinLimitForBand(i) ||
params.maxTxBps[i] > PriceMath.getMaxLimitForBand(i)) revert InvalidParams();
}
}
if (params.feeSplit.count > 0) {
if (params.feeSplit.count > 5) revert InvalidFeeSplit();
uint256 totalPercentage = 0;
for (uint8 i = 0; i < params.feeSplit.count; i++) {
if (params.feeSplit.wallets[i] == address(0)) revert ZeroAddressInSplit();
totalPercentage += params.feeSplit.percentages[i];
}
if (totalPercentage != BPS) revert InvalidFeeSplit();
}
// Anti-snipe tax: if enabled, the dev-chosen tax must be within [40%, 80%].
// When disabled it is ignored (no sniper window).
if (params.sniperTaxEnabled) {
if (params.sniperTaxBps < SNIPER_MIN_BPS || params.sniperTaxBps > SNIPER_MAX_BPS) {
revert InvalidSniperTax();
}
}
// Deploy token (branded/unbranded) with a chain-bound CREATE2 salt.
token = _deployToken(params);
PoolKey memory key = _createPoolKey(token);
uint256 totalSupply = TOTAL_SUPPLY;
uint160 sqrtPriceX96 = PriceMath.calculateSqrtPrice(totalSupply, key.tickSpacing);
poolId = key.toId();
poolManager.initialize(key, sqrtPriceX96);
hook.registerLaunch(key, token, params.beneficiary,
params.lpFeesPerBand, params.maxWalletBps, params.maxTxBps,
params.mode, params.timeConfig,
params.sniperTaxEnabled, params.sniperTaxBps);
(int24[8] memory tickLowers, int24[8] memory tickUppers, uint256[8] memory allocations) =
PriceMath.calculateBandRanges(totalSupply, key.tickSpacing);
uint256[8] memory posTokenIds;
bool[8] memory mintedFlags;
_tryMintBand(key, 0, tickLowers, tickUppers, allocations, sqrtPriceX96, bootstrapAmount, posTokenIds, mintedFlags);
for (uint256 i = 1; i < 8; i++) {
_tryMintBand(key, i, tickLowers, tickUppers, allocations, sqrtPriceX96, 0, posTokenIds, mintedFlags);
}
PoolState memory poolState = PoolState({
token: token,
beneficiary: params.beneficiary,
totalSupply: totalSupply,
positionTokenIds: posTokenIds,
positionMinted: mintedFlags,
recycledETH: 0,
activated: true
});
poolStates[poolId] = poolState;
for (uint256 i = 0; i < 8; i++) {
if (mintedFlags[i]) {
tokenIdToPoolId[posTokenIds[i]] = poolId;
}
}
LaunchInfo memory info = LaunchInfo({
token: token,
beneficiary: params.beneficiary,
creator: msg.sender,
poolId: poolId,
poolKey: key,
createdAt: block.timestamp,
createdBlock: block.number,
name: params.name,
symbol: params.symbol,
feeSplit: params.feeSplit,
lpFeesPerBand: params.lpFeesPerBand,
maxWalletBps: params.maxWalletBps,
maxTxBps: params.maxTxBps,
branded: params.branded
});
_launchByToken[token] = info;
_launchByPoolId[poolId] = info;
totalLaunches++;
emit LiquidityAdded(token, poolId, allocations[0], posTokenIds[0]);
emit TokenLaunched(
token,
params.beneficiary,
msg.sender,
poolId,
sqrtPriceX96,
params.name,
params.symbol
);
antiPvpRegistry.registerCombo(params.name, params.symbol);
return (token, poolId);
}
function _tryMintBand(
PoolKey memory key,
uint256 bandIndex,
int24[8] memory tickLowers,
int24[8] memory tickUppers,
uint256[8] memory allocations,
uint160 sqrtPriceX96,
uint256 ethForPosition,
uint256[8] memory posTokenIds,
bool[8] memory mintedFlags
) internal {
uint128 liq = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickLowers[bandIndex]),
TickMath.getSqrtPriceAtTick(tickUppers[bandIndex]),
ethForPosition,
allocations[bandIndex]
);
if (liq > 0) {
posTokenIds[bandIndex] = _mintPositionViaManager(
key,
tickLowers[bandIndex],
tickUppers[bandIndex],
allocations[bandIndex],
ethForPosition
);
mintedFlags[bandIndex] = true;
}
}
function _mintPositionViaManager(
PoolKey memory key,
int24 tickLower,
int24 tickUpper,
uint256 tokenAmount,
uint256 ethAmount
) internal returns (uint256 tokenId) {
address token = Currency.unwrap(key.currency1);
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
_getPoolPrice(key),
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
ethAmount,
tokenAmount
);
if (liquidity == 0) revert ZeroLiquidity();
SteakHouseV4Token(token).approve(PERMIT2, tokenAmount);
IAllowanceTransfer(PERMIT2).approve(token, positionManager, type(uint160).max, type(uint48).max);
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(key, tickLower, tickUpper, uint256(liquidity), uint128(ethAmount), uint128(tokenAmount), address(this), bytes(""));
params[1] = abi.encode(key.currency0);
params[2] = abi.encode(key.currency1);
bytes memory unlockData = abi.encode(
abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.CLOSE_CURRENCY), uint8(Actions.CLOSE_CURRENCY)),
params
);
IPositionManager(positionManager).modifyLiquidities{value: ethAmount}(unlockData, block.timestamp + 1 hours);
return IPositionManager(positionManager).nextTokenId() - 1;
}
function _getPoolPrice(PoolKey memory key) internal view returns (uint160) {
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(key.toId());
return sqrtPriceX96;
}
function _withdrawPositionViaManager(
uint256 tokenId
) internal returns (uint256 ethAmount, uint256 tokenAmount) {
uint128 liquidity = IPositionManager(positionManager).getPositionLiquidity(tokenId);
if (liquidity == 0) revert ZeroLiquidity();
PoolId poolId = tokenIdToPoolId[tokenId];
if (PoolId.unwrap(poolId) == bytes32(0)) revert PoolIdNotFound();
LaunchInfo storage launch = _launchByPoolId[poolId];
PoolKey memory key = launch.poolKey;
uint256 ethBefore = address(this).balance;
address token = Currency.unwrap(key.currency1);
uint256 tokenBefore = SteakHouseV4Token(token).balanceOf(address(this));
bytes memory actions = abi.encodePacked(
uint8(Actions.DECREASE_LIQUIDITY),
uint8(Actions.BURN_POSITION),
uint8(Actions.CLOSE_CURRENCY),
uint8(Actions.CLOSE_CURRENCY)
);
bytes[] memory params = new bytes[](4);
params[0] = abi.encode(tokenId, uint256(liquidity), uint128(0), uint128(0), bytes(""));
params[1] = abi.encode(tokenId, uint128(0), uint128(0), bytes(""));
params[2] = abi.encode(key.currency0);
params[3] = abi.encode(key.currency1);
bytes memory unlockData = abi.encode(actions, params);
uint256 deadline = block.timestamp + 1 hours;
IPositionManager(positionManager).modifyLiquidities(unlockData, deadline);
ethAmount = address(this).balance - ethBefore;
tokenAmount = SteakHouseV4Token(token).balanceOf(address(this)) - tokenBefore;
return (ethAmount, tokenAmount);
}
function _distributeFees(
uint256 amount,
bool isEth,
address token,
LaunchInfo storage info,
PoolId poolId
) private {
if (amount == 0) return;
// Platform share is owner-configurable in config (0..50%); remainder to beneficiary.
uint256 platformShare = (amount * config.platformShareBps()) / BPS;
uint256 beneficiaryShare = amount - platformShare;
if (platformShare > 0) {
if (isEth) {
_payEthOrPark(poolId, config.treasury(), platformShare);
} else {
SteakHouseV4Token(token).transfer(config.treasury(), platformShare);
}
}
if (beneficiaryShare > 0) {
if (info.feeSplit.count > 0) {
for (uint8 i = 0; i < info.feeSplit.count; i++) {
address wallet = info.feeSplit.wallets[i];
uint256 walletShare = (beneficiaryShare * info.feeSplit.percentages[i]) / BPS;
if (walletShare > 0) {
if (isEth) {
if (_payEthOrPark(poolId, wallet, walletShare)) {
emit FeeSplitDistributed(poolId, wallet, walletShare);
}
} else {
SteakHouseV4Token(token).transfer(wallet, walletShare);
}
}
}
} else {
if (isEth) {
_payEthOrPark(poolId, info.beneficiary, beneficiaryShare);
} else {
SteakHouseV4Token(token).transfer(info.beneficiary, beneficiaryShare);
}
}
}
}
/// @notice Send `amount` ETH to `to`; if the send reverts (recipient rejects
/// ETH or runs out of gas), park it in failedFeeETH[poolId] and
/// continue instead of reverting the whole collection. A recipient
/// can therefore only ever cause ITS OWN share to be parked — never
/// steal another recipient's share or block collection. Returns true
/// on a successful send. Capped gas stipend prevents a griefer from
/// burning collection gas.
function _payEthOrPark(PoolId poolId, address to, uint256 amount) private returns (bool) {
(bool ok,) = to.call{value: amount, gas: 60_000}("");
if (!ok) {
failedFeeETH[poolId] += amount;
emit FeeSendFailed(poolId, to, amount);
}
return ok;
}
/// @notice Owner recovers ETH parked by _payEthOrPark for a pool (e.g. after
/// the offending recipient is fixed, or to forward it manually).
/// Sends to `to`; reverts if that send fails so nothing is lost.
function recoverFailedFees(PoolId poolId, address to) external onlyOwner nonReentrant {
uint256 amount = failedFeeETH[poolId];
if (amount == 0) revert InvalidParams();
if (to == address(0)) revert ZeroBeneficiary();
failedFeeETH[poolId] = 0;
(bool ok,) = to.call{value: amount}("");
if (!ok) revert TransferFailed();
emit FailedFeesRecovered(poolId, to, amount);
}
function launchByToken(address token) external view returns (LaunchInfo memory) {
return _launchByToken[token];
}
function launchByPoolId(PoolId poolId) external view returns (LaunchInfo memory) {
return _launchByPoolId[poolId];
}
function poolActivated(PoolId poolId) external view returns (bool) {
return poolStates[poolId].activated;
}
function positionTokenId(PoolId poolId) external view returns (uint256) {
return poolStates[poolId].positionTokenIds[0];
}
function previewSqrtPrice(int24 tickSpacing) external pure returns (uint160) {
return PriceMath.calculateSqrtPrice(TOTAL_SUPPLY, tickSpacing);
}
function _deployToken(CreateParams calldata params) internal returns (address token) {
bytes memory code = params.branded
? type(SteakHouseV4Token).creationCode
: type(Token).creationCode;
bytes memory args = abi.encode(params.name, params.symbol, address(this), params.beneficiary);
bytes32 salt = _tokenSalt(
params.vanity ? params.customSalt : bytes32(0),
msg.sender,
params.name,
params.symbol
);
return _deployWithCreate2(code, args, salt);
}
function _tokenSalt(
bytes32 userSalt,
address creator,
string calldata name,
string calldata symbol
) internal view returns (bytes32) {
return keccak256(abi.encode(
"STEAKHOUSE_TOKEN_V1",
block.chainid,
address(this),
creator,
totalLaunches,
name,
symbol,
userSalt
));
}
/**
* @notice Deploy contract using CREATE2 with the effective token salt
* @param creationCode Contract bytecode
* @param constructorArgs Encoded constructor arguments
* @param salt Effective salt for CREATE2
* @return deployed Deployed contract address
*/
function _deployWithCreate2(
bytes memory creationCode,
bytes memory constructorArgs,
bytes32 salt
) internal returns (address deployed) {
bytes memory bytecode = abi.encodePacked(creationCode, constructorArgs);
assembly {
deployed := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
}
if (deployed == address(0)) revert InvalidSalt();
return deployed;
}
function _createPoolKey(address token) internal view returns (PoolKey memory) {
Currency currency0 = Currency.wrap(address(0));
Currency currency1 = Currency.wrap(token);
return PoolKey({
currency0: currency0,
currency1: currency1,
fee: 0x800000,
tickSpacing: 60,
hooks: IHooks(address(hook))
});
}
function collectFeesFromPosition(PoolId poolId, uint256 positionIndex) external nonReentrant {
PoolState storage state = poolStates[poolId];
if (positionIndex >= 8) revert InvalidPosition();
if (!state.positionMinted[positionIndex]) revert NotMinted();
uint256 tokenId = state.positionTokenIds[positionIndex];
if (tokenId == 0) revert InvalidTokenId();
LaunchInfo storage info = _launchByPoolId[poolId];
PoolKey memory key = info.poolKey;
// Permissionless (DIRECT V4 parity): payouts only ever go to the fixed
// treasury/feeSplit/beneficiary recipients, so anyone — including a
// ProofOfSteak staking contract registered as a feeSplit wallet — may
// trigger collection.
uint256 ethBefore = address(this).balance;
uint256 tokenBefore = SteakHouseV4Token(info.token).balanceOf(address(this));
bytes memory actions = abi.encodePacked(
uint8(Actions.DECREASE_LIQUIDITY),
uint8(Actions.CLOSE_CURRENCY),
uint8(Actions.CLOSE_CURRENCY)
);
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(tokenId, uint256(0), uint128(0), uint128(0), bytes(""));
params[1] = abi.encode(key.currency0);
params[2] = abi.encode(key.currency1);
bytes memory unlockData = abi.encode(actions, params);
uint256 deadline = block.timestamp + 1 hours;
IPositionManager(positionManager).modifyLiquidities(unlockData, deadline);
uint256 ethGained = address(this).balance - ethBefore;
uint256 tokenGained = SteakHouseV4Token(info.token).balanceOf(address(this)) - tokenBefore;
_distributeFees(ethGained, true, info.token, info, poolId);
_distributeFees(tokenGained, false, info.token, info, poolId);
emit FeesCollectedFromPosition(poolId, positionIndex, tokenId, ethGained, tokenGained);
}
}
Chain explorer3050msChain node85ms