LaunchLocker

0x7f03effbd7ceb22a3f80dd468f67ef27826acd85

Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
4,823 bytes
ABI entries
39
9 read · 9 write
License
none

Contract information

Address
0x7f03effbd7ceb22a3f80dd468f67ef27826acd85
Chain
Robinhood Chain (4663)
Compiler
v0.8.30+commit.73712a01
Optimization
Enabled
Creation tx
0x359a038b1f…61c71695f8

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (9)

deployerTokens(address, uint256)address
factory()address
feeCollectors(address)bool
feeRedirects(address)address
getLaunchedToken(address)tuple
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
protocolFeeRecipient()address
protocolFeeShare()uint256

Events (8)

FactoryUpdatedFeeCollectorUpdatedFeeRedirectUpdatedFeesClaimedOwnershipTransferredPositionLockedProtocolFeeRecipientUpdatedProtocolFeeUpdated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_protocolFeeRecipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "_protocolFeeShare",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyInitialized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidProtocolFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoFeesToCollect",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAuthorized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotDeployer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFactory",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PositionNotHeld",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TokenNotFound",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "newFactory",
        "type": "address"
      }
    ],
    "name": "FactoryUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "collector",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "status",
        "type": "bool"
      }
    ],
    "name": "FeeCollectorUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "FeeRedirectUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token0",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token1",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "recipientAmount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "recipientAmount1",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolAmount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolAmount1",
        "type": "uint256"
      }
    ],
    "name": "FeesClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pairedToken",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "dexId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "positionManager",
        "type": "address"
      }
    ],
    "name": "PositionLocked",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "newRecipient",
        "type": "address"
      }
    ],
    "name": "ProtocolFeeRecipientUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "ProtocolFeeUpdated",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "collectFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "deployerTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "feeCollectors",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "feeRedirects",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getLaunchedToken",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "token",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "deployer",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pairedToken",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "dexId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "launchConfigId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "restrictionsEndBlock",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "supply",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "isToken0",
            "type": "bool"
          },
          {
            "internalType": "uint24",
            "name": "poolFee",
            "type": "uint24"
          },
          {
            "internalType": "bool",
            "name": "exists",
            "type": "bool"
          },
          {
            "internalType": "uint256",
            "name": "initialBuyAmount",
            "type": "uint256"
          }
        ],
        "internalType": "struct LauncherTypes.LaunchedToken",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_factory",
        "type": "address"
      }
    ],
    "name": "initialize",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "lockPosition",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolFeeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolFeeShare",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "collector",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "status",
        "type": "bool"
      }
    ],
    "name": "setFeeCollector",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "setFeeRedirect",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_protocolFeeRecipient",
        "type": "address"
      }
    ],
    "name": "setProtocolFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_protocolFeeShare",
        "type": "uint256"
      }
    ],
    "name": "setProtocolFeeShare",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

library LauncherTypes {
    struct LaunchedToken {
        address token;
        address deployer;
        address pairedToken;
        address positionManager;
        uint256 positionId;
        uint256 dexId;
        uint256 launchConfigId;
        uint256 restrictionsEndBlock;
        uint256 supply;
        bool isToken0;
        uint24 poolFee;
        bool exists;
        uint256 initialBuyAmount;
    }
}

interface ILaunchFactory {
    function getLaunchedToken(address token) external view returns (LauncherTypes.LaunchedToken memory);
}

interface INonfungiblePositionManager {
    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    function ownerOf(uint256 tokenId) external view returns (address);

    function positions(uint256 tokenId)
        external
        view
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    function collect(CollectParams calldata params) external returns (uint256 amount0, uint256 amount1);
}

contract LaunchLocker is Ownable, ReentrancyGuard, IERC721Receiver {
    error AlreadyInitialized();
    error InvalidProtocolFee();
    error NoFeesToCollect();
    error NotAuthorized();
    error NotDeployer();
    error NotFactory();
    error PositionNotHeld();
    error TokenNotFound();
    error ZeroAddress();

    event FactoryUpdated(address indexed newFactory);
    event FeeCollectorUpdated(address indexed collector, bool status);
    event FeeRedirectUpdated(address indexed token, address indexed recipient);
    event FeesClaimed(
        address indexed token,
        address indexed caller,
        address token0,
        address token1,
        uint256 recipientAmount0,
        uint256 recipientAmount1,
        uint256 protocolAmount0,
        uint256 protocolAmount1
    );
    event PositionLocked(
        address indexed token,
        address indexed deployer,
        uint256 positionId,
        address pairedToken,
        uint256 indexed dexId,
        address positionManager
    );
    event ProtocolFeeRecipientUpdated(address newRecipient);
    event ProtocolFeeUpdated(uint256 newFee);

    uint256 public protocolFeeShare;
    address public protocolFeeRecipient;
    address public factory;

    mapping(address => address[]) public deployerTokens;
    mapping(address => bool) public feeCollectors;
    mapping(address => address) public feeRedirects;

    constructor(address _protocolFeeRecipient, uint256 _protocolFeeShare) Ownable(msg.sender) {
        if (_protocolFeeRecipient == address(0)) revert ZeroAddress();
        if (_protocolFeeShare > 100) revert InvalidProtocolFee();

        protocolFeeRecipient = _protocolFeeRecipient;
        protocolFeeShare = _protocolFeeShare;
    }

    function initialize(address _factory) external onlyOwner {
        if (_factory == address(0)) revert ZeroAddress();
        if (factory != address(0)) revert AlreadyInitialized();

        factory = _factory;
        emit FactoryUpdated(_factory);
    }

    function getLaunchedToken(address token) public view returns (LauncherTypes.LaunchedToken memory) {
        return ILaunchFactory(factory).getLaunchedToken(token);
    }

    function onERC721Received(address, address from, uint256, bytes calldata)
        external
        view
        override
        returns (bytes4)
    {
        if (from != factory) revert NotFactory();
        return IERC721Receiver.onERC721Received.selector;
    }

    function lockPosition(address token) external {
        if (msg.sender != factory) revert NotFactory();

        LauncherTypes.LaunchedToken memory launched = ILaunchFactory(factory).getLaunchedToken(token);
        if (!launched.exists || launched.token == address(0)) revert TokenNotFound();
        if (
            launched.deployer == address(0) || launched.pairedToken == address(0)
                || launched.positionManager == address(0) || launched.token != token
        ) revert TokenNotFound();

        if (INonfungiblePositionManager(launched.positionManager).ownerOf(launched.positionId) != address(this)) {
            revert PositionNotHeld();
        }

        deployerTokens[launched.deployer].push(token);
        emit PositionLocked(
            token,
            launched.deployer,
            launched.positionId,
            launched.pairedToken,
            launched.dexId,
            launched.positionManager
        );
    }

    function collectFees(address token) external nonReentrant {
        LauncherTypes.LaunchedToken memory launched = ILaunchFactory(factory).getLaunchedToken(token);
        if (!launched.exists) revert TokenNotFound();
        if (msg.sender != owner() && msg.sender != launched.deployer && !feeCollectors[msg.sender]) {
            revert NotAuthorized();
        }

        INonfungiblePositionManager manager = INonfungiblePositionManager(launched.positionManager);
        (uint256 amount0, uint256 amount1) = manager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: launched.positionId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        if (amount0 == 0 && amount1 == 0) revert NoFeesToCollect();

        (,, address token0, address token1,,,,,,,,) = manager.positions(launched.positionId);

        uint256 protocolAmount0 = (amount0 * protocolFeeShare) / 100;
        uint256 protocolAmount1 = (amount1 * protocolFeeShare) / 100;
        uint256 recipientAmount0 = amount0 - protocolAmount0;
        uint256 recipientAmount1 = amount1 - protocolAmount1;

        address recipient = feeRedirects[token] != address(0) ? feeRedirects[token] : launched.deployer;

        if (amount0 != 0) {
            IERC20(token0).transfer(protocolFeeRecipient, protocolAmount0);
            IERC20(token0).transfer(recipient, recipientAmount0);
        }
        if (amount1 != 0) {
            IERC20(token1).transfer(protocolFeeRecipient, protocolAmount1);
            IERC20(token1).transfer(recipient, recipientAmount1);
        }

        emit FeesClaimed(
            token,
            msg.sender,
            token0,
            token1,
            recipientAmount0,
            recipientAmount1,
            protocolAmount0,
            protocolAmount1
        );
    }

    function setFeeRedirect(address token, address recipient) external {
        LauncherTypes.LaunchedToken memory launched = ILaunchFactory(factory).getLaunchedToken(token);
        if (!launched.exists) revert TokenNotFound();
        if (msg.sender != launched.deployer) revert NotDeployer();

        feeRedirects[token] = recipient;
        emit FeeRedirectUpdated(token, recipient);
    }

    function setFeeCollector(address collector, bool status) external onlyOwner {
        if (collector == address(0)) revert ZeroAddress();
        feeCollectors[collector] = status;
        emit FeeCollectorUpdated(collector, status);
    }

    function setProtocolFeeShare(uint256 _protocolFeeShare) external onlyOwner {
        if (_protocolFeeShare > 100) revert InvalidProtocolFee();
        protocolFeeShare = _protocolFeeShare;
        emit ProtocolFeeUpdated(_protocolFeeShare);
    }

    function setProtocolFeeRecipient(address _protocolFeeRecipient) external onlyOwner {
        if (_protocolFeeRecipient == address(0)) revert ZeroAddress();
        protocolFeeRecipient = _protocolFeeRecipient;
        emit ProtocolFeeRecipientUpdated(_protocolFeeRecipient);
    }
}
Chain explorer3322msChain node82ms