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
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