HoodlLaunchController
0xa7946c7c6224ec8f8759dc864a02319fe0f69825
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
15,433 bytes
ABI entries
69
16 read · 11 write
License
none
Contract information
- Address
- 0xa7946c7c6224ec8f8759dc864a02319fe0f69825
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x0Dc1Dd32B1…A5c0c14550
- Creation tx
- 0xe1caef40a7…c96c65e473
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (16)
BURN_ADDRESS() → address
boughtInFairLaunch(bytes32, address) → uint256
fairLaunchInfo(bytes32) → tuple
hoodl() → address
hook() → address
inFairLaunchWindow(bytes32) → bool
isFairLaunchOpen(bytes32) → bool
isWhitelisted(bytes32, address, uint256, bytes32[]) → bool
launchPoolId() → bytes32
launchPoolKeySet() → bool
marketCapToPrice(uint256, uint256) → uint256
owner() → address
poolManager() → address
quotePayment(bytes32, uint256) → uint256
quoteTokensForPayment(bytes32, uint256) → uint256
whitelistLeaf(address, uint256) → bytes32
Events (12)
FairLaunchBuyTaxSettledFairLaunchCloseShortfallHandledFairLaunchClosedFairLaunchConfiguredFairLaunchFilledFairLaunchLiquidityCreatedFairLaunchRescuedHookSetLaunchPoolKeySetNativeWithdrawnOwnershipTransferredPoolInitialized
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "hoodl_",
"type": "address"
},
{
"internalType": "address",
"name": "poolManager_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AmountTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "BadFairLaunchWindow",
"type": "error"
},
{
"inputs": [],
"name": "BadLiquidityRange",
"type": "error"
},
{
"inputs": [],
"name": "ExceedsFairLaunchSupply",
"type": "error"
},
{
"inputs": [],
"name": "ExceedsWalletCap",
"type": "error"
},
{
"inputs": [],
"name": "ExceedsWhitelistAllocation",
"type": "error"
},
{
"inputs": [],
"name": "FairLaunchAlreadyStarted",
"type": "error"
},
{
"inputs": [],
"name": "FairLaunchClosedAlready",
"type": "error"
},
{
"inputs": [],
"name": "FairLaunchHookLocked",
"type": "error"
},
{
"inputs": [],
"name": "FairLaunchNotActive",
"type": "error"
},
{
"inputs": [],
"name": "FairLaunchNotConfigured",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "startsAt",
"type": "uint256"
}
],
"name": "FairLaunchNotStarted",
"type": "error"
},
{
"inputs": [],
"name": "FairLaunchStillActive",
"type": "error"
},
{
"inputs": [],
"name": "IncorrectNativePayment",
"type": "error"
},
{
"inputs": [],
"name": "InitialTickOutOfRange",
"type": "error"
},
{
"inputs": [],
"name": "InvalidFairLaunchAmount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPoolHook",
"type": "error"
},
{
"inputs": [],
"name": "InvalidProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTaxQuote",
"type": "error"
},
{
"inputs": [],
"name": "OnlyHook",
"type": "error"
},
{
"inputs": [],
"name": "OnlyNativeHoodlPool",
"type": "error"
},
{
"inputs": [],
"name": "OnlyOwner",
"type": "error"
},
{
"inputs": [],
"name": "OnlyPoolManager",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint160",
"name": "actualSqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "actualTick",
"type": "int24"
},
{
"internalType": "uint160",
"name": "expectedSqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "expectedTick",
"type": "int24"
}
],
"name": "PoolPriceMismatch",
"type": "error"
},
{
"inputs": [],
"name": "ReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "TransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "ZeroLiquidity",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "taxBaseAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalTax",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "teamTax",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "sheriffTax",
"type": "uint256"
}
],
"name": "FairLaunchBuyTaxSettled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nativeRequested",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nativeUsed",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenLiquidityRequested",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenLiquidityUsed",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "unsoldRequested",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "unsoldBurned",
"type": "uint256"
}
],
"name": "FairLaunchCloseShortfallHandled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "revenue",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "sold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "unsoldSupply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenLiquiditySupply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "endedAt",
"type": "uint256"
}
],
"name": "FairLaunchClosed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint64",
"name": "startsAt",
"type": "uint64"
},
{
"indexed": false,
"internalType": "uint64",
"name": "endsAt",
"type": "uint64"
},
{
"indexed": false,
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint256",
"name": "pricePerToken",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fairLaunchSupply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "maxPerWallet",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenLiquiditySupply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "merkleRoot",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bool",
"name": "whitelistRequired",
"type": "bool"
}
],
"name": "FairLaunchConfigured",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paymentAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalPayment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "revenue",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "remainingSupply",
"type": "uint256"
}
],
"name": "FairLaunchFilled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "tokenIsCurrency0",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
}
],
"name": "FairLaunchLiquidityCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nativeAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "FairLaunchRescued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldHook",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newHook",
"type": "address"
}
],
"name": "HookSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"name": "LaunchPoolKeySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "NativeWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "PoolInitialized",
"type": "event"
},
{
"inputs": [],
"name": "BURN_ADDRESS",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "buyer",
"type": "address"
}
],
"name": "boughtInFairLaunch",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxWhitelistAllocation",
"type": "uint256"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "buyExactTokens",
"outputs": [
{
"internalType": "uint256",
"name": "paymentIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalPayment",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "maxWhitelistAllocation",
"type": "uint256"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "buyWithPayment",
"outputs": [
{
"internalType": "uint256",
"name": "paymentIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "refund",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"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": "key",
"type": "tuple"
},
{
"internalType": "uint64",
"name": "startsAt",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "endsAt",
"type": "uint64"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"internalType": "uint256",
"name": "pricePerToken",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fairLaunchSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxPerWallet",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenLiquiditySupply",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "merkleRoot",
"type": "bytes32"
},
{
"internalType": "bool",
"name": "whitelistRequired",
"type": "bool"
}
],
"name": "configureFairLaunch",
"outputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "int24",
"name": "initializedTick",
"type": "int24"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"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": "key",
"type": "tuple"
},
{
"internalType": "uint64",
"name": "startsAt",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "endsAt",
"type": "uint64"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"internalType": "uint256",
"name": "targetMarketCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenSupplyForMarketCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fairLaunchSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxPerWallet",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenLiquiditySupply",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "merkleRoot",
"type": "bytes32"
},
{
"internalType": "bool",
"name": "whitelistRequired",
"type": "bool"
}
],
"name": "createMarketCapFairLaunch",
"outputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "int24",
"name": "initializedTick",
"type": "int24"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"name": "fairLaunchInfo",
"outputs": [
{
"components": [
{
"internalType": "uint64",
"name": "startsAt",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "endsAt",
"type": "uint64"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"internalType": "uint256",
"name": "pricePerToken",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "remainingSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "sold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "revenue",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxPerWallet",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenLiquiditySupply",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "merkleRoot",
"type": "bytes32"
},
{
"internalType": "bool",
"name": "whitelistRequired",
"type": "bool"
},
{
"internalType": "bool",
"name": "closed",
"type": "bool"
}
],
"internalType": "struct HoodlLaunchController.FairLaunchInfo",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "finalizeFairLaunch",
"outputs": [
{
"internalType": "uint256",
"name": "revenue",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nativeLiquidityAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenLiquidityAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "unsoldBurnAmount",
"type": "uint256"
},
{
"internalType": "bool",
"name": "closedNow",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"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": "key",
"type": "tuple"
}
],
"name": "finalizeFairLaunchFromHook",
"outputs": [
{
"internalType": "uint256",
"name": "revenue",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nativeLiquidityAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenLiquidityAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "unsoldBurnAmount",
"type": "uint256"
},
{
"internalType": "bool",
"name": "closedNow",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "hoodl",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "hook",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"name": "inFairLaunchWindow",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"name": "isFairLaunchOpen",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "maxWhitelistAllocation",
"type": "uint256"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "isWhitelisted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchPoolId",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchPoolKeySet",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "targetMarketCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenSupplyForMarketCap",
"type": "uint256"
}
],
"name": "marketCapToPrice",
"outputs": [
{
"internalType": "uint256",
"name": "pricePerToken",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "quotePayment",
"outputs": [
{
"internalType": "uint256",
"name": "paymentAmount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "paymentAmount",
"type": "uint256"
}
],
"name": "quoteTokensForPayment",
"outputs": [
{
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "nativeAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "rescueFairLaunch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newHook",
"type": "address"
}
],
"name": "setHook",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "maxWhitelistAllocation",
"type": "uint256"
}
],
"name": "whitelistLeaf",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawNative",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { IPoolManager } from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import { IUnlockCallback } from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import { IERC20Minimal } from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol";
import { FixedPoint96 } from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import { FullMath } from "@uniswap/v4-core/src/libraries/FullMath.sol";
import { StateLibrary } from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import { TickMath } from "@uniswap/v4-core/src/libraries/TickMath.sol";
import { BalanceDelta, BalanceDeltaLibrary } from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import { Currency } from "@uniswap/v4-core/src/types/Currency.sol";
import { ModifyLiquidityParams } from "@uniswap/v4-core/src/types/PoolOperation.sol";
import { PoolId, PoolIdLibrary } from "@uniswap/v4-core/src/types/PoolId.sol";
import { PoolKey } from "@uniswap/v4-core/src/types/PoolKey.sol";
interface IHOODLLaunchToken is IERC20Minimal {
function hoodlGame() external view returns (address);
function teamWallet() external view returns (address);
}
interface IHOODLLaunchGame {
function fundSheriffPot(address from, uint256 amount) external payable;
function grossPaymentForNetBuy(address trader, uint256 netPayment)
external
view
returns (uint256 grossPayment, uint256 totalTax, uint256 teamTax, uint256 sheriffTax);
function quoteBuyTaxIncludedForGross(address trader, uint256 grossPayment)
external
view
returns (uint256 totalTax, uint256 teamTax, uint256 sheriffTax, uint256 netPayment);
function scheduleFirstStealUnlock() external returns (uint256 unlockAt);
}
interface IHOODLLaunchHook {
function enableTrading(PoolKey calldata key) external;
}
/// @title HoodlLaunchController
/// @notice Standalone fair-launch and v4 bootstrap module for HOODL.
contract HoodlLaunchController is IUnlockCallback {
using BalanceDeltaLibrary for BalanceDelta;
using PoolIdLibrary for PoolKey;
struct FairLaunchInfo {
uint64 startsAt;
uint64 endsAt;
int24 initialTick;
uint256 pricePerToken;
uint256 remainingSupply;
uint256 sold;
uint256 revenue;
uint256 maxPerWallet;
uint256 tokenLiquiditySupply;
bytes32 merkleRoot;
bool whitelistRequired;
bool closed;
}
address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
address public immutable hoodl;
address public immutable poolManager;
address public owner;
address public hook;
bytes32 public launchPoolId;
bool public launchPoolKeySet;
PoolKey private launchKey;
mapping(bytes32 poolId => FairLaunchInfo info) private fairLaunches;
mapping(bytes32 poolId => mapping(address buyer => uint256 amount)) public boughtInFairLaunch;
uint256 private reentrancyStatus;
struct BootstrapData {
PoolKey key;
bytes32 poolId;
uint256 nativeLiquidityAmount;
uint256 tokenLiquidityAmount;
int24 initialTick;
}
error AmountTooLarge();
error BadFairLaunchWindow();
error BadLiquidityRange();
error ExceedsFairLaunchSupply();
error ExceedsWalletCap();
error ExceedsWhitelistAllocation();
error FairLaunchAlreadyStarted();
error FairLaunchClosedAlready();
error FairLaunchStillActive();
error FairLaunchHookLocked();
error FairLaunchNotConfigured();
error FairLaunchNotActive();
error FairLaunchNotStarted(uint256 startsAt);
error IncorrectNativePayment();
error InitialTickOutOfRange();
error InvalidFairLaunchAmount();
error InvalidProof();
error InvalidPoolHook();
error InvalidTaxQuote();
error OnlyNativeHoodlPool();
error OnlyHook();
error OnlyPoolManager();
error OnlyOwner();
error PoolPriceMismatch(
uint160 actualSqrtPriceX96,
int24 actualTick,
uint160 expectedSqrtPriceX96,
int24 expectedTick
);
error ReentrantCall();
error TransferFailed();
error ZeroLiquidity();
error ZeroAddress();
event FairLaunchBuyTaxSettled(
bytes32 indexed poolId,
address indexed buyer,
uint256 taxBaseAmount,
uint256 totalTax,
uint256 teamTax,
uint256 sheriffTax
);
event FairLaunchClosed(
bytes32 indexed poolId,
uint256 revenue,
uint256 sold,
uint256 unsoldSupply,
uint256 tokenLiquiditySupply,
uint256 endedAt
);
event FairLaunchConfigured(
bytes32 indexed poolId,
uint64 startsAt,
uint64 endsAt,
int24 initialTick,
uint256 pricePerToken,
uint256 fairLaunchSupply,
uint256 maxPerWallet,
uint256 tokenLiquiditySupply,
bytes32 merkleRoot,
bool whitelistRequired
);
event FairLaunchCloseShortfallHandled(
bytes32 indexed poolId,
uint256 nativeRequested,
uint256 nativeUsed,
uint256 tokenLiquidityRequested,
uint256 tokenLiquidityUsed,
uint256 unsoldRequested,
uint256 unsoldBurned
);
event FairLaunchFilled(
bytes32 indexed poolId,
address indexed buyer,
uint256 tokenAmount,
uint256 paymentAmount,
uint256 totalPayment,
uint256 revenue,
uint256 remainingSupply
);
event FairLaunchLiquidityCreated(
bytes32 indexed poolId,
int24 tickLower,
int24 tickUpper,
uint256 tokenAmount,
bool tokenIsCurrency0,
uint128 liquidity
);
event FairLaunchRescued(
bytes32 indexed poolId, address indexed recipient, uint256 nativeAmount, uint256 tokenAmount
);
event HookSet(address indexed oldHook, address indexed newHook);
event LaunchPoolKeySet(bytes32 indexed poolId);
event NativeWithdrawn(address indexed recipient, uint256 amount);
event PoolInitialized(bytes32 indexed poolId, uint160 sqrtPriceX96, int24 tick);
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
modifier onlyOwner() {
_onlyOwner();
_;
}
modifier nonReentrant() {
if (reentrancyStatus == 1) revert ReentrantCall();
reentrancyStatus = 1;
_;
reentrancyStatus = 0;
}
function _onlyOwner() internal view {
if (msg.sender != owner) revert OnlyOwner();
}
constructor(address hoodl_, address poolManager_) {
if (hoodl_ == address(0) || poolManager_ == address(0)) revert ZeroAddress();
hoodl = hoodl_;
poolManager = poolManager_;
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
receive() external payable { }
function setHook(address newHook) external onlyOwner {
if (newHook == address(0)) revert ZeroAddress();
_requireHookMutable();
address oldHook = hook;
hook = newHook;
emit HookSet(oldHook, newHook);
}
function configureFairLaunch(
PoolKey calldata key,
uint64 startsAt,
uint64 endsAt,
int24 initialTick,
uint256 pricePerToken,
uint256 fairLaunchSupply,
uint256 maxPerWallet,
uint256 tokenLiquiditySupply,
bytes32 merkleRoot,
bool whitelistRequired
) external onlyOwner returns (bytes32 poolId, int24 initializedTick) {
(poolId, initializedTick) = _configureFairLaunch(
key,
startsAt,
endsAt,
initialTick,
pricePerToken,
fairLaunchSupply,
maxPerWallet,
tokenLiquiditySupply,
merkleRoot,
whitelistRequired
);
}
function createMarketCapFairLaunch(
PoolKey calldata key,
uint64 startsAt,
uint64 endsAt,
int24 initialTick,
uint256 targetMarketCap,
uint256 tokenSupplyForMarketCap,
uint256 fairLaunchSupply,
uint256 maxPerWallet,
uint256 tokenLiquiditySupply,
bytes32 merkleRoot,
bool whitelistRequired
) external onlyOwner returns (bytes32 poolId, int24 initializedTick) {
uint256 pricePerToken = marketCapToPrice(targetMarketCap, tokenSupplyForMarketCap);
(poolId, initializedTick) = _configureFairLaunch(
key,
startsAt,
endsAt,
initialTick,
pricePerToken,
fairLaunchSupply,
maxPerWallet,
tokenLiquiditySupply,
merkleRoot,
whitelistRequired
);
}
function _configureFairLaunch(
PoolKey calldata key,
uint64 startsAt,
uint64 endsAt,
int24 initialTick,
uint256 pricePerToken,
uint256 fairLaunchSupply,
uint256 maxPerWallet,
uint256 tokenLiquiditySupply,
bytes32 merkleRoot,
bool whitelistRequired
) internal returns (bytes32 poolId, int24 initializedTick) {
_requireNativeHoodlPool(key);
_requireBootstrapableTick(initialTick, key.tickSpacing);
poolId = PoolId.unwrap(key.toId());
if (poolId == bytes32(0)) revert FairLaunchNotConfigured();
FairLaunchInfo storage existing = fairLaunches[poolId];
if (existing.startsAt != 0 && block.timestamp >= existing.startsAt) {
revert FairLaunchAlreadyStarted();
}
_validateFairLaunch(
startsAt,
endsAt,
pricePerToken,
fairLaunchSupply,
maxPerWallet,
merkleRoot,
whitelistRequired
);
launchKey = key;
launchPoolId = poolId;
launchPoolKeySet = true;
fairLaunches[poolId] = FairLaunchInfo({
startsAt: startsAt,
endsAt: endsAt,
initialTick: initialTick,
pricePerToken: pricePerToken,
remainingSupply: fairLaunchSupply,
sold: 0,
revenue: 0,
maxPerWallet: maxPerWallet,
tokenLiquiditySupply: tokenLiquiditySupply,
merkleRoot: merkleRoot,
whitelistRequired: whitelistRequired,
closed: false
});
emit FairLaunchConfigured(
poolId,
startsAt,
endsAt,
initialTick,
pricePerToken,
fairLaunchSupply,
maxPerWallet,
tokenLiquiditySupply,
merkleRoot,
whitelistRequired
);
emit LaunchPoolKeySet(poolId);
initializedTick = _initializeOrValidatePool(key, initialTick);
}
function fairLaunchInfo(bytes32 poolId) external view returns (FairLaunchInfo memory) {
return fairLaunches[poolId];
}
function isFairLaunchOpen(bytes32 poolId) external view returns (bool) {
FairLaunchInfo memory info = fairLaunches[poolId];
return _isConfigured(info) && !info.closed;
}
function inFairLaunchWindow(bytes32 poolId) external view returns (bool) {
return _inFairLaunchWindow(fairLaunches[poolId]);
}
function buyWithPayment(
bytes32 poolId,
uint256 maxWhitelistAllocation,
bytes32[] calldata proof
) external payable nonReentrant returns (uint256 paymentIn, uint256 tokensOut, uint256 refund) {
if (msg.value == 0) revert InvalidFairLaunchAmount();
IHOODLLaunchGame game = _game();
uint256 totalPayment = msg.value;
uint256 teamTax;
uint256 sheriffTax;
(uint256 totalTax, uint256 quotedTeamTax, uint256 quotedSheriffTax, uint256 netPayment) =
game.quoteBuyTaxIncludedForGross(msg.sender, totalPayment);
teamTax = quotedTeamTax;
sheriffTax = quotedSheriffTax;
tokensOut = quoteTokensForPayment(poolId, netPayment);
FairLaunchInfo storage info = fairLaunches[poolId];
if (tokensOut > info.remainingSupply) {
tokensOut = info.remainingSupply;
paymentIn = quotePayment(poolId, tokensOut);
(totalPayment, totalTax, teamTax, sheriffTax) =
game.grossPaymentForNetBuy(msg.sender, paymentIn);
// Quote round-trips are not guaranteed wei-exact; absorb any excess
// from the revenue leg instead of letting the sellout buy revert
// on refund underflow.
if (totalPayment > msg.value) {
uint256 shortfall = totalPayment - msg.value;
if (paymentIn <= shortfall) revert InvalidTaxQuote();
paymentIn -= shortfall;
totalPayment = msg.value;
}
refund = msg.value - totalPayment;
} else {
paymentIn = netPayment;
}
_fillFairLaunchBuy(
poolId,
msg.sender,
paymentIn,
tokensOut,
totalPayment,
totalTax,
teamTax,
sheriffTax,
maxWhitelistAllocation,
proof
);
if (refund != 0) {
_sendNative(msg.sender, refund);
}
}
function buyExactTokens(
bytes32 poolId,
uint256 tokenAmount,
uint256 maxWhitelistAllocation,
bytes32[] calldata proof
) external payable nonReentrant returns (uint256 paymentIn, uint256 totalPayment) {
paymentIn = quotePayment(poolId, tokenAmount);
uint256 totalTax;
uint256 teamTax;
uint256 sheriffTax;
(totalPayment, totalTax, teamTax, sheriffTax) =
_game().grossPaymentForNetBuy(msg.sender, paymentIn);
if (msg.value != totalPayment) revert IncorrectNativePayment();
_fillFairLaunchBuy(
poolId,
msg.sender,
paymentIn,
tokenAmount,
totalPayment,
totalTax,
teamTax,
sheriffTax,
maxWhitelistAllocation,
proof
);
}
function finalizeFairLaunch()
external
nonReentrant
returns (
uint256 revenue,
uint256 nativeLiquidityAmount,
uint256 tokenLiquidityAmount,
uint256 unsoldBurnAmount,
bool closedNow
)
{
PoolKey memory key = _storedLaunchKey();
return _finalizeFairLaunch(key, false);
}
function finalizeFairLaunchFromHook(PoolKey calldata key)
external
nonReentrant
returns (
uint256 revenue,
uint256 nativeLiquidityAmount,
uint256 tokenLiquidityAmount,
uint256 unsoldBurnAmount,
bool closedNow
)
{
if (msg.sender != hook) revert OnlyHook();
return _finalizeFairLaunch(key, true);
}
function _finalizeFairLaunch(PoolKey memory key, bool poolManagerAlreadyUnlocked)
internal
returns (
uint256 revenue,
uint256 nativeLiquidityAmount,
uint256 tokenLiquidityAmount,
uint256 unsoldBurnAmount,
bool closedNow
)
{
if (hook == address(0)) revert ZeroAddress();
_requireNativeHoodlPool(key);
bytes32 poolId = PoolId.unwrap(key.toId());
FairLaunchInfo storage info = fairLaunches[poolId];
if (!_isConfigured(info)) revert FairLaunchNotConfigured();
revenue = info.revenue;
uint256 tokenLiquiditySupply = info.tokenLiquiditySupply;
uint256 unsoldSupply = info.remainingSupply;
int24 initialTick = info.initialTick;
if (info.closed) {
return (revenue, 0, 0, 0, false);
}
if (block.timestamp < info.endsAt && info.remainingSupply != 0) {
revert FairLaunchStillActive();
}
_initializeOrValidatePool(key, initialTick);
info.closed = true;
info.endsAt = uint64(block.timestamp);
closedNow = true;
nativeLiquidityAmount = _min(revenue, address(this).balance);
uint256 tokenBalance = IERC20Minimal(hoodl).balanceOf(address(this));
tokenLiquidityAmount = _min(tokenLiquiditySupply, tokenBalance);
unsoldBurnAmount = _min(unsoldSupply, tokenBalance - tokenLiquidityAmount);
if (nativeLiquidityAmount != 0 || tokenLiquidityAmount != 0) {
BootstrapData memory bootstrap = BootstrapData({
key: key,
poolId: poolId,
nativeLiquidityAmount: nativeLiquidityAmount,
tokenLiquidityAmount: tokenLiquidityAmount,
initialTick: initialTick
});
if (poolManagerAlreadyUnlocked) {
_bootstrapFairLaunchLiquidity(bootstrap);
} else {
IPoolManager(poolManager).unlock(abi.encode(bootstrap));
}
}
if (unsoldBurnAmount != 0) {
_safeTransfer(hoodl, BURN_ADDRESS, unsoldBurnAmount);
}
if (
nativeLiquidityAmount != revenue || tokenLiquidityAmount != tokenLiquiditySupply
|| unsoldBurnAmount != unsoldSupply
) {
emit FairLaunchCloseShortfallHandled(
poolId,
revenue,
nativeLiquidityAmount,
tokenLiquiditySupply,
tokenLiquidityAmount,
unsoldSupply,
unsoldBurnAmount
);
}
IHOODLLaunchHook(hook).enableTrading(key);
_game().scheduleFirstStealUnlock();
emit FairLaunchClosed(
poolId, revenue, info.sold, unsoldSupply, tokenLiquiditySupply, block.timestamp
);
}
/// @notice Owner escape hatch for any ETH held by the controller, bounded only by
/// balance — unlike `rescueFairLaunch` it works regardless of launch state and does
/// not touch launch accounting. Intended for unaccounted ETH (e.g. force-sent);
/// pre-close launch revenue should be recovered with `rescueFairLaunch` so
/// `revenue` stays consistent.
function withdrawNative(address recipient, uint256 amount)
external
onlyOwner
nonReentrant
{
if (recipient == address(0)) revert ZeroAddress();
if (amount == 0 || amount > address(this).balance) revert InvalidFairLaunchAmount();
_sendNative(recipient, amount);
emit NativeWithdrawn(recipient, amount);
}
function rescueFairLaunch(
bytes32 poolId,
address recipient,
uint256 nativeAmount,
uint256 tokenAmount
) external onlyOwner nonReentrant {
if (recipient == address(0)) revert ZeroAddress();
if (nativeAmount == 0 && tokenAmount == 0) revert InvalidFairLaunchAmount();
FairLaunchInfo storage info = fairLaunches[poolId];
if (!_isConfigured(info)) revert FairLaunchNotConfigured();
if (info.closed) revert FairLaunchClosedAlready();
if (
nativeAmount > info.revenue || nativeAmount > address(this).balance
|| tokenAmount > _rescuableTokenSupply(info)
|| tokenAmount > IERC20Minimal(hoodl).balanceOf(address(this))
) {
revert InvalidFairLaunchAmount();
}
if (nativeAmount != 0) {
info.revenue -= nativeAmount;
_sendNative(recipient, nativeAmount);
}
if (tokenAmount != 0) {
_reduceLaunchTokenAccounting(info, tokenAmount);
_safeTransfer(hoodl, recipient, tokenAmount);
}
emit FairLaunchRescued(poolId, recipient, nativeAmount, tokenAmount);
}
function marketCapToPrice(uint256 targetMarketCap, uint256 tokenSupplyForMarketCap)
public
pure
returns (uint256 pricePerToken)
{
if (targetMarketCap == 0 || tokenSupplyForMarketCap == 0) {
revert InvalidFairLaunchAmount();
}
pricePerToken = FullMath.mulDiv(targetMarketCap, 1 ether, tokenSupplyForMarketCap);
if (pricePerToken == 0) revert InvalidFairLaunchAmount();
}
function quotePayment(bytes32 poolId, uint256 tokenAmount)
public
view
returns (uint256 paymentAmount)
{
FairLaunchInfo memory info = fairLaunches[poolId];
if (!_isConfigured(info)) revert FairLaunchNotConfigured();
if (tokenAmount == 0) revert InvalidFairLaunchAmount();
paymentAmount = FullMath.mulDivRoundingUp(tokenAmount, info.pricePerToken, 1 ether);
}
function quoteTokensForPayment(bytes32 poolId, uint256 paymentAmount)
public
view
returns (uint256 tokenAmount)
{
FairLaunchInfo memory info = fairLaunches[poolId];
if (!_isConfigured(info)) revert FairLaunchNotConfigured();
if (paymentAmount == 0) revert InvalidFairLaunchAmount();
tokenAmount = FullMath.mulDiv(paymentAmount, 1 ether, info.pricePerToken);
if (tokenAmount == 0) revert InvalidFairLaunchAmount();
}
function whitelistLeaf(address account, uint256 maxWhitelistAllocation)
public
pure
returns (bytes32)
{
// forge-lint: disable-next-line(asm-keccak256)
return keccak256(abi.encodePacked(account, maxWhitelistAllocation));
}
function isWhitelisted(
bytes32 poolId,
address account,
uint256 maxWhitelistAllocation,
bytes32[] calldata proof
) external view returns (bool) {
FairLaunchInfo memory info = fairLaunches[poolId];
if (!info.whitelistRequired) {
return true;
}
return _verify(proof, info.merkleRoot, whitelistLeaf(account, maxWhitelistAllocation));
}
function transferOwnership(address newOwner) external onlyOwner {
if (newOwner == address(0)) revert ZeroAddress();
address oldOwner = owner;
owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
function unlockCallback(bytes calldata data) external override returns (bytes memory) {
if (msg.sender != poolManager) revert OnlyPoolManager();
BootstrapData memory bootstrap = abi.decode(data, (BootstrapData));
_bootstrapFairLaunchLiquidity(bootstrap);
return "";
}
function _bootstrapFairLaunchLiquidity(BootstrapData memory bootstrap) internal {
bool hoodlIsCurrency0 = Currency.unwrap(bootstrap.key.currency0) == hoodl;
int24 tickSpacing = bootstrap.key.tickSpacing;
if (hoodlIsCurrency0) {
int24 revenueUpper = _floorToSpacing(bootstrap.initialTick - 1, tickSpacing);
int24 revenueLower = revenueUpper - tickSpacing;
_createSingleSidedPosition(
bootstrap.key,
bootstrap.poolId,
revenueLower,
revenueUpper,
bootstrap.nativeLiquidityAmount,
false
);
int24 tokenLower = _ceilToSpacing(bootstrap.initialTick + 1, tickSpacing);
int24 tokenUpper = TickMath.maxUsableTick(tickSpacing);
_createSingleSidedPosition(
bootstrap.key,
bootstrap.poolId,
tokenLower,
tokenUpper,
bootstrap.tokenLiquidityAmount,
true
);
} else {
int24 revenueLower = _ceilToSpacing(bootstrap.initialTick + 1, tickSpacing);
int24 revenueUpper = revenueLower + tickSpacing;
_createSingleSidedPosition(
bootstrap.key,
bootstrap.poolId,
revenueLower,
revenueUpper,
bootstrap.nativeLiquidityAmount,
true
);
int24 tokenLower = TickMath.minUsableTick(tickSpacing);
int24 tokenUpper = _floorToSpacing(bootstrap.initialTick - 1, tickSpacing);
_createSingleSidedPosition(
bootstrap.key,
bootstrap.poolId,
tokenLower,
tokenUpper,
bootstrap.tokenLiquidityAmount,
false
);
}
}
function _fillFairLaunchBuy(
bytes32 poolId,
address buyer,
uint256 paymentIn,
uint256 tokensOut,
uint256 totalPayment,
uint256 totalTax,
uint256 teamTax,
uint256 sheriffTax,
uint256 maxWhitelistAllocation,
bytes32[] calldata proof
) internal {
FairLaunchInfo storage info = fairLaunches[poolId];
if (!_isConfigured(info) || info.closed) revert FairLaunchNotConfigured();
_validateBuyTaxQuote(totalPayment, paymentIn, totalTax, teamTax, sheriffTax);
if (block.timestamp < info.startsAt) revert FairLaunchNotStarted(info.startsAt);
if (!_inFairLaunchWindow(info)) revert FairLaunchNotActive();
if (paymentIn == 0 || tokensOut == 0 || totalPayment == 0) {
revert InvalidFairLaunchAmount();
}
if (tokensOut > info.remainingSupply) revert ExceedsFairLaunchSupply();
uint256 bought = boughtInFairLaunch[poolId][buyer] + tokensOut;
if (bought > info.maxPerWallet) revert ExceedsWalletCap();
if (info.whitelistRequired) {
if (!_verify(proof, info.merkleRoot, whitelistLeaf(buyer, maxWhitelistAllocation))) {
revert InvalidProof();
}
if (bought > maxWhitelistAllocation) revert ExceedsWhitelistAllocation();
}
boughtInFairLaunch[poolId][buyer] = bought;
info.remainingSupply -= tokensOut;
info.sold += tokensOut;
info.revenue += paymentIn;
_safeTransfer(hoodl, buyer, tokensOut);
_distributeBuyTax(poolId, buyer, paymentIn, totalTax, teamTax, sheriffTax);
emit FairLaunchFilled(
poolId, buyer, tokensOut, paymentIn, totalPayment, info.revenue, info.remainingSupply
);
if (info.remainingSupply == 0 && poolId == launchPoolId) {
_finalizeFairLaunch(_storedLaunchKey(), false);
}
}
function _distributeBuyTax(
bytes32 poolId,
address buyer,
uint256 paymentIn,
uint256 totalTax,
uint256 teamTax,
uint256 sheriffTax
) internal {
if (totalTax == 0) {
return;
}
if (teamTax != 0) {
_sendNative(IHOODLLaunchToken(hoodl).teamWallet(), teamTax);
}
if (sheriffTax != 0) {
_game().fundSheriffPot{ value: sheriffTax }(buyer, sheriffTax);
}
emit FairLaunchBuyTaxSettled(poolId, buyer, paymentIn, totalTax, teamTax, sheriffTax);
}
function _validateBuyTaxQuote(
uint256 totalPayment,
uint256 paymentIn,
uint256 totalTax,
uint256 teamTax,
uint256 sheriffTax
) internal pure {
if (
teamTax > totalTax || sheriffTax != totalTax - teamTax || totalTax > totalPayment
|| paymentIn != totalPayment - totalTax
) {
revert InvalidTaxQuote();
}
}
function _createSingleSidedPosition(
PoolKey memory key,
bytes32 poolId,
int24 tickLower,
int24 tickUpper,
uint256 tokenAmount,
bool tokenIsCurrency0
) internal {
if (tokenAmount == 0) {
return;
}
if (tickLower >= tickUpper) revert BadLiquidityRange();
uint160 sqrtPriceLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtPriceUpper = TickMath.getSqrtPriceAtTick(tickUpper);
uint128 liquidity = tokenIsCurrency0
? _liquidityForAmount0(sqrtPriceLower, sqrtPriceUpper, tokenAmount)
: _liquidityForAmount1(sqrtPriceLower, sqrtPriceUpper, tokenAmount);
if (liquidity == 0) revert ZeroLiquidity();
(BalanceDelta delta,) = IPoolManager(poolManager)
.modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: int256(uint256(liquidity)),
salt: bytes32(0)
}),
""
);
_settleNegativeDelta(key.currency0, delta.amount0());
_settleNegativeDelta(key.currency1, delta.amount1());
emit FairLaunchLiquidityCreated(
poolId, tickLower, tickUpper, tokenAmount, tokenIsCurrency0, liquidity
);
}
function _settleNegativeDelta(Currency currency, int128 delta) internal {
if (delta < 0) {
_settleCurrency(currency, uint256(uint128(-delta)));
}
}
function _settleCurrency(Currency currency, uint256 amount) internal {
if (amount == 0) {
return;
}
IPoolManager manager = IPoolManager(poolManager);
if (Currency.unwrap(currency) == address(0)) {
manager.settle{ value: amount }();
} else {
manager.sync(currency);
_safeTransfer(Currency.unwrap(currency), address(manager), amount);
manager.settle();
}
}
function _game() internal view returns (IHOODLLaunchGame) {
address game = IHOODLLaunchToken(hoodl).hoodlGame();
if (game == address(0)) revert ZeroAddress();
return IHOODLLaunchGame(game);
}
function _storedLaunchKey() internal view returns (PoolKey memory key) {
if (!launchPoolKeySet) revert FairLaunchNotConfigured();
key = launchKey;
}
function _requireHookMutable() internal view {
if (!launchPoolKeySet) {
return;
}
FairLaunchInfo memory info = fairLaunches[launchPoolId];
if (_isConfigured(info) && !info.closed && block.timestamp >= info.startsAt) {
revert FairLaunchHookLocked();
}
}
function _requireNativeHoodlPool(PoolKey memory key) internal view {
address configuredHook = hook;
if (configuredHook == address(0)) revert ZeroAddress();
if (address(key.hooks) != configuredHook) revert InvalidPoolHook();
bool hoodlIsCurrency0 = Currency.unwrap(key.currency0) == hoodl;
bool hoodlIsCurrency1 = Currency.unwrap(key.currency1) == hoodl;
bool nativeIsCurrency0 = Currency.unwrap(key.currency0) == address(0);
bool nativeIsCurrency1 = Currency.unwrap(key.currency1) == address(0);
if (!((hoodlIsCurrency0 && nativeIsCurrency1) || (hoodlIsCurrency1 && nativeIsCurrency0))) {
revert OnlyNativeHoodlPool();
}
}
function _initializeOrValidatePool(PoolKey memory key, int24 initialTick)
internal
returns (int24 actualTick)
{
IPoolManager manager = IPoolManager(poolManager);
uint160 expectedSqrtPriceX96 = TickMath.getSqrtPriceAtTick(initialTick);
(uint160 actualSqrtPriceX96, int24 observedTick,,) =
StateLibrary.getSlot0(manager, key.toId());
actualTick = observedTick;
if (actualSqrtPriceX96 == 0) {
actualTick = manager.initialize(key, expectedSqrtPriceX96);
if (actualTick != initialTick) {
revert PoolPriceMismatch(0, actualTick, expectedSqrtPriceX96, initialTick);
}
emit PoolInitialized(PoolId.unwrap(key.toId()), expectedSqrtPriceX96, actualTick);
return actualTick;
}
if (actualSqrtPriceX96 != expectedSqrtPriceX96 || actualTick != initialTick) {
revert PoolPriceMismatch(
actualSqrtPriceX96, actualTick, expectedSqrtPriceX96, initialTick
);
}
}
function _rescuableTokenSupply(FairLaunchInfo storage info) internal view returns (uint256) {
return info.tokenLiquiditySupply + info.remainingSupply;
}
function _reduceLaunchTokenAccounting(FairLaunchInfo storage info, uint256 tokenAmount)
internal
{
uint256 liquidityReduction = _min(tokenAmount, info.tokenLiquiditySupply);
if (liquidityReduction != 0) {
info.tokenLiquiditySupply -= liquidityReduction;
}
uint256 remainingReduction = tokenAmount - liquidityReduction;
if (remainingReduction != 0) {
info.remainingSupply -= remainingReduction;
}
}
function _sendNative(address recipient, uint256 amount) internal {
if (recipient == address(0)) revert ZeroAddress();
(bool sent,) = payable(recipient).call{ value: amount }("");
if (!sent) revert TransferFailed();
}
function _safeTransfer(address token, address to, uint256 amount) internal {
bool success = IERC20Minimal(token).transfer(to, amount);
if (!success) revert TransferFailed();
}
function _min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function _liquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
internal
pure
returns (uint128 liquidity)
{
if (sqrtPriceAX96 > sqrtPriceBX96) {
(sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
}
uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
liquidity =
_toUint128(FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96));
}
function _liquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
internal
pure
returns (uint128 liquidity)
{
if (sqrtPriceAX96 > sqrtPriceBX96) {
(sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
}
liquidity =
_toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96));
}
/// @dev Both close-time single-sided bands must fit inside the usable tick range:
/// one full spacing-wide band below `floor(initialTick - 1)` and one above
/// `ceil(initialTick + 1)`, whichever side each currency ordering assigns them.
/// Validating here means `_bootstrapFairLaunchLiquidity` can never revert on
/// out-of-range ticks and wedge the lazy-finalize path.
function _requireBootstrapableTick(int24 initialTick, int24 tickSpacing) internal pure {
if (
_floorToSpacing(initialTick - 1, tickSpacing) - tickSpacing
< TickMath.minUsableTick(tickSpacing)
|| _ceilToSpacing(initialTick + 1, tickSpacing) + tickSpacing
> TickMath.maxUsableTick(tickSpacing)
) {
revert InitialTickOutOfRange();
}
}
function _floorToSpacing(int24 tick, int24 tickSpacing) internal pure returns (int24) {
int256 tick256 = int256(tick);
int256 spacing256 = int256(tickSpacing);
int256 compressed = tick256 / spacing256;
if (tick256 < 0 && tick256 % spacing256 != 0) {
compressed--;
}
return int24(compressed * spacing256);
}
function _ceilToSpacing(int24 tick, int24 tickSpacing) internal pure returns (int24) {
int256 tick256 = int256(tick);
int256 spacing256 = int256(tickSpacing);
int256 compressed = tick256 / spacing256;
if (tick256 > 0 && tick256 % spacing256 != 0) {
compressed++;
}
return int24(compressed * spacing256);
}
function _toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) revert AmountTooLarge();
return uint128(value);
}
function _validateFairLaunch(
uint64 startsAt,
uint64 endsAt,
uint256 pricePerToken,
uint256 fairLaunchSupply,
uint256 maxPerWallet,
bytes32 merkleRoot,
bool whitelistRequired
) internal pure {
if (startsAt >= endsAt) revert BadFairLaunchWindow();
if (pricePerToken == 0 || fairLaunchSupply == 0 || maxPerWallet == 0) {
revert InvalidFairLaunchAmount();
}
if (maxPerWallet > fairLaunchSupply) revert ExceedsFairLaunchSupply();
if (whitelistRequired && merkleRoot == bytes32(0)) revert InvalidProof();
}
function _isConfigured(FairLaunchInfo memory info) internal pure returns (bool) {
return info.startsAt != 0;
}
function _inFairLaunchWindow(FairLaunchInfo memory info) internal view returns (bool) {
return block.timestamp >= info.startsAt && block.timestamp < info.endsAt
&& info.remainingSupply > 0;
}
function _verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
internal
pure
returns (bool)
{
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
computedHash = computedHash <= proofElement
? keccak256(abi.encodePacked(computedHash, proofElement))
: keccak256(abi.encodePacked(proofElement, computedHash));
}
return computedHash == root;
}
}
Chain explorer2124msChain node98ms