LaunchLocker
0xfd9759b27dcfb339a62e3c052fc43879b4d56dcd
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
6,410 bytes
ABI entries
40
10 read · 8 write
License
mit
Contract information
- Address
- 0xfd9759b27dcfb339a62e3c052fc43879b4d56dcd
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xF04AA0C4FE…046FA4E0b2
- Creation tx
- 0xc0a8a2ef4f…4a72a97b4a
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (10)
BPS_DENOMINATOR() → uint16
creatorClaimable(address, address) → uint256
creatorFeeReceiver(address) → address
creatorFeeShareBps() → uint16
launchpad() → address
onERC721Received(address, address, uint256, bytes) → bytes4
positionManager() → address
positions(address) → uint256, address, address, address, address, bool
treasury() → address
treasuryClaimable(address) → uint256
Events (8)
BlacklistedFeesRedirectedCreatorFeeReceiverTransferredCreatorFeeShareSetCreatorFeesClaimedFeesCollectedPositionRegisteredTreasuryFeesClaimedTreasurySet
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "address",
"name": "launchpad_",
"type": "address"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
},
{
"internalType": "uint16",
"name": "creatorFeeShareBps_",
"type": "uint16"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyRegistered",
"type": "error"
},
{
"inputs": [],
"name": "CreatorIsBlacklisted",
"type": "error"
},
{
"inputs": [],
"name": "CreatorIsNotBlacklisted",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientClaimable",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBps",
"type": "error"
},
{
"inputs": [],
"name": "OnlyCreatorFeeReceiver",
"type": "error"
},
{
"inputs": [],
"name": "OnlyLaunchpad",
"type": "error"
},
{
"inputs": [],
"name": "OnlyTreasury",
"type": "error"
},
{
"inputs": [],
"name": "Reentrancy",
"type": "error"
},
{
"inputs": [],
"name": "TokenCallFailed",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedNft",
"type": "error"
},
{
"inputs": [],
"name": "UnknownLaunch",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "BlacklistedFeesRedirected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "previousReceiver",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newReceiver",
"type": "address"
}
],
"name": "CreatorFeeReceiverTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "creatorFeeShareBps",
"type": "uint16"
}
],
"name": "CreatorFeeShareSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creatorFeeReceiver",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "CreatorFeesClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "creatorAmount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "creatorAmount1",
"type": "uint256"
}
],
"name": "FeesCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token1",
"type": "address"
}
],
"name": "PositionRegistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TreasuryFeesClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "treasury",
"type": "address"
}
],
"name": "TreasurySet",
"type": "event"
},
{
"inputs": [],
"name": "BPS_DENOMINATOR",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "claimCreatorFees",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "claimTreasuryFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
}
],
"name": "collectFees",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "creatorClaimable",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
}
],
"name": "creatorFeeReceiver",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "creatorFeeShareBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchpad",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "onERC721Received",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionManager",
"outputs": [
{
"internalType": "contract INonfungiblePositionManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
}
],
"name": "positions",
"outputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "address",
"name": "creatorFeeReceiver",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"internalType": "bool",
"name": "registered",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
}
],
"name": "redirectBlacklistedCreatorFees",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "address",
"name": "creatorFeeReceiver_",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
}
],
"name": "registerPosition",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "creatorFeeShareBps_",
"type": "uint16"
}
],
"name": "setCreatorFeeShareBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "treasury_",
"type": "address"
}
],
"name": "setTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launchToken",
"type": "address"
},
{
"internalType": "address",
"name": "newReceiver",
"type": "address"
}
],
"name": "transferCreatorFeeReceiver",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "treasuryClaimable",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;
import {
IERC721Receiver,
ILaunchpadBlacklist,
INonfungiblePositionManager,
SafeTransferLib
} from "./LaunchpadInterfaces.sol";
// ************************************************
// _ _ __ __ ____ ____ ____ _ _ ____ _ _ ____
// / )( \ / \ / \( \( __)( \ ( \/ )( __)( \/ )( __)
// ) __ (( O )( O )) D ( ) _) ) D ( _ / \/ \ ) _) / \/ \ ) _)
// \_)(_/ \__/ \__/(____/(____)(____/(_)\_)(_/(____)\_)(_/(____)
//
// Website: https://hooded.meme
// Author: SwapHood Team (kellkell)
//
// ************************************************
/// @notice Permanently holds launchpad LP NFTs and accounts the fees without exposing liquidity withdrawal.
contract LaunchLocker is IERC721Receiver {
using SafeTransferLib for address;
error OnlyLaunchpad();
error OnlyTreasury();
error OnlyCreatorFeeReceiver();
error InvalidAddress();
error InvalidBps();
error UnknownLaunch();
error AlreadyRegistered();
error CreatorIsBlacklisted();
error CreatorIsNotBlacklisted();
error InsufficientClaimable();
error Reentrancy();
error UnexpectedNft();
struct Position {
uint256 tokenId;
address creator;
address creatorFeeReceiver;
address token0;
address token1;
bool registered;
}
uint16 public constant BPS_DENOMINATOR = 10_000;
INonfungiblePositionManager public immutable positionManager;
address public immutable launchpad;
address public treasury;
uint16 public creatorFeeShareBps;
mapping(address launchToken => Position position) public positions;
mapping(address launchToken => mapping(address asset => uint256 amount)) public creatorClaimable;
mapping(address asset => uint256 amount) public treasuryClaimable;
uint256 private _locked = 1;
event PositionRegistered(
address indexed launchToken,
uint256 indexed tokenId,
address indexed creator,
address token0,
address token1
);
event FeesCollected(
address indexed launchToken,
uint256 amount0,
uint256 amount1,
uint256 creatorAmount0,
uint256 creatorAmount1
);
event CreatorFeesClaimed(
address indexed launchToken,
address indexed creatorFeeReceiver,
address indexed to,
uint256 amount0,
uint256 amount1
);
event CreatorFeeReceiverTransferred(
address indexed launchToken,
address indexed previousReceiver,
address indexed newReceiver
);
event BlacklistedFeesRedirected(address indexed launchToken, uint256 amount0, uint256 amount1);
event TreasuryFeesClaimed(address indexed asset, address indexed to, uint256 amount);
event CreatorFeeShareSet(uint16 creatorFeeShareBps);
event TreasurySet(address indexed treasury);
modifier onlyLaunchpad() {
if (msg.sender != launchpad) revert OnlyLaunchpad();
_;
}
modifier nonReentrant() {
if (_locked != 1) revert Reentrancy();
_locked = 2;
_;
_locked = 1;
}
constructor(
address positionManager_,
address launchpad_,
address treasury_,
uint16 creatorFeeShareBps_
) {
if (positionManager_ == address(0) || launchpad_ == address(0) || treasury_ == address(0)) {
revert InvalidAddress();
}
if (creatorFeeShareBps_ > BPS_DENOMINATOR) revert InvalidBps();
positionManager = INonfungiblePositionManager(positionManager_);
launchpad = launchpad_;
treasury = treasury_;
creatorFeeShareBps = creatorFeeShareBps_;
// Constructor logs let the launchpad indexer reconstruct the initial locker
// configuration from the deployment transaction alone.
emit TreasurySet(treasury_);
emit CreatorFeeShareSet(creatorFeeShareBps_);
}
function registerPosition(
address launchToken,
uint256 tokenId,
address creator,
address creatorFeeReceiver_,
address token0,
address token1
) external onlyLaunchpad {
if (launchToken == address(0) || creator == address(0) || creatorFeeReceiver_ == address(0)) {
revert InvalidAddress();
}
if (positions[launchToken].registered) revert AlreadyRegistered();
positions[launchToken] =
Position(tokenId, creator, creatorFeeReceiver_, token0, token1, true);
emit PositionRegistered(launchToken, tokenId, creator, token0, token1);
emit CreatorFeeReceiverTransferred(launchToken, address(0), creatorFeeReceiver_);
}
/// @notice Transfers all present and future creator-fee rights for a launch.
/// @dev Claimable balances are keyed by launch token, so already-accounted fees follow the right.
function transferCreatorFeeReceiver(address launchToken, address newReceiver) external {
Position storage position = positions[launchToken];
if (!position.registered) revert UnknownLaunch();
if (msg.sender != position.creatorFeeReceiver) revert OnlyCreatorFeeReceiver();
if (newReceiver == address(0)) revert InvalidAddress();
address previousReceiver = position.creatorFeeReceiver;
position.creatorFeeReceiver = newReceiver;
emit CreatorFeeReceiverTransferred(launchToken, previousReceiver, newReceiver);
}
function creatorFeeReceiver(address launchToken) external view returns (address) {
Position storage position = positions[launchToken];
if (!position.registered) revert UnknownLaunch();
return position.creatorFeeReceiver;
}
/// @notice Realizes all currently earned fees and assigns the configured creator/treasury split.
function collectFees(address launchToken)
external
nonReentrant
returns (uint256 amount0, uint256 amount1)
{
Position memory position = positions[launchToken];
if (!position.registered) revert UnknownLaunch();
(amount0, amount1) = _collectFees(launchToken, position);
}
/// @notice Realizes the latest LP fees, pays the creator share, and sends the treasury
/// share for both pool assets directly to the configured treasury in one transaction.
/// @dev The treasury sweep includes previously-accounted balances for the same assets.
function claimCreatorFees(address launchToken, address to)
external
nonReentrant
returns (uint256 amount0, uint256 amount1)
{
Position memory position = positions[launchToken];
if (!position.registered) revert UnknownLaunch();
if (msg.sender != position.creatorFeeReceiver) revert OnlyCreatorFeeReceiver();
if (to == address(0)) revert InvalidAddress();
if (ILaunchpadBlacklist(launchpad).creatorBlacklisted(position.creator)) revert CreatorIsBlacklisted();
_collectFees(launchToken, position);
amount0 = creatorClaimable[launchToken][position.token0];
amount1 = creatorClaimable[launchToken][position.token1];
uint256 treasuryAmount0 = treasuryClaimable[position.token0];
uint256 treasuryAmount1 = treasuryClaimable[position.token1];
creatorClaimable[launchToken][position.token0] = 0;
creatorClaimable[launchToken][position.token1] = 0;
treasuryClaimable[position.token0] = 0;
treasuryClaimable[position.token1] = 0;
if (amount0 != 0) position.token0.safeTransfer(to, amount0);
if (amount1 != 0) position.token1.safeTransfer(to, amount1);
emit CreatorFeesClaimed(launchToken, position.creatorFeeReceiver, to, amount0, amount1);
if (treasuryAmount0 != 0) position.token0.safeTransfer(treasury, treasuryAmount0);
if (treasuryAmount1 != 0) position.token1.safeTransfer(treasury, treasuryAmount1);
emit TreasuryFeesClaimed(position.token0, treasury, treasuryAmount0);
emit TreasuryFeesClaimed(position.token1, treasury, treasuryAmount1);
}
/// @notice Moves already-accounted creator fees to treasury after that creator is blacklisted.
function redirectBlacklistedCreatorFees(address launchToken)
external
returns (uint256 amount0, uint256 amount1)
{
Position memory position = positions[launchToken];
if (!position.registered) revert UnknownLaunch();
if (!ILaunchpadBlacklist(launchpad).creatorBlacklisted(position.creator)) {
revert CreatorIsNotBlacklisted();
}
amount0 = creatorClaimable[launchToken][position.token0];
amount1 = creatorClaimable[launchToken][position.token1];
creatorClaimable[launchToken][position.token0] = 0;
creatorClaimable[launchToken][position.token1] = 0;
treasuryClaimable[position.token0] += amount0;
treasuryClaimable[position.token1] += amount1;
emit BlacklistedFeesRedirected(launchToken, amount0, amount1);
}
function claimTreasuryFees(address asset, address to, uint256 amount) external nonReentrant {
if (msg.sender != treasury) revert OnlyTreasury();
if (to == address(0)) revert InvalidAddress();
uint256 claimable = treasuryClaimable[asset];
if (amount > claimable) revert InsufficientClaimable();
treasuryClaimable[asset] = claimable - amount;
if (amount != 0) asset.safeTransfer(to, amount);
emit TreasuryFeesClaimed(asset, to, amount);
}
function setCreatorFeeShareBps(uint16 creatorFeeShareBps_) external onlyLaunchpad {
if (creatorFeeShareBps_ > BPS_DENOMINATOR) revert InvalidBps();
creatorFeeShareBps = creatorFeeShareBps_;
emit CreatorFeeShareSet(creatorFeeShareBps_);
}
function setTreasury(address treasury_) external onlyLaunchpad {
if (treasury_ == address(0)) revert InvalidAddress();
treasury = treasury_;
emit TreasurySet(treasury_);
}
function _collectFees(address launchToken, Position memory position)
internal
returns (uint256 amount0, uint256 amount1)
{
(amount0, amount1) = positionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: position.tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
uint256 creatorAmount0;
uint256 creatorAmount1;
if (!ILaunchpadBlacklist(launchpad).creatorBlacklisted(position.creator)) {
creatorAmount0 = (amount0 * creatorFeeShareBps) / BPS_DENOMINATOR;
creatorAmount1 = (amount1 * creatorFeeShareBps) / BPS_DENOMINATOR;
creatorClaimable[launchToken][position.token0] += creatorAmount0;
creatorClaimable[launchToken][position.token1] += creatorAmount1;
}
treasuryClaimable[position.token0] += amount0 - creatorAmount0;
treasuryClaimable[position.token1] += amount1 - creatorAmount1;
emit FeesCollected(launchToken, amount0, amount1, creatorAmount0, creatorAmount1);
}
function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
if (msg.sender != address(positionManager)) revert UnexpectedNft();
return IERC721Receiver.onERC721Received.selector;
}
}
Chain explorer2063msChain node84ms