LauncherLocker

0x173d8370b4f67535d406f2f46168ec48aa03d26e

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
2,673 bytes
ABI entries
25
6 read · 6 write
License
none

Contract information

Address
0x173d8370b4f67535d406f2f46168ec48aa03d26e
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x210545dc6e…90859f16f2

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (6)

factory()address
feeRouter()address
feeWalletOf(address)address
getPosition(address)tuple
onERC721Received(address, address, uint256, bytes)bytes4
owner()address

Events (5)

FactoryUpdatedFeeRedirectUpdatedFeesClaimedOwnershipTransferredPositionRegistered

ABI

[
  {
    "inputs": [
      {
        "internalType": "address payable",
        "name": "feeRouter_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyRegistered",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFactory",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFeeWallet",
    "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": "UnknownToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "factory",
        "type": "address"
      }
    ],
    "name": "FactoryUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newFeeWallet",
        "type": "address"
      }
    ],
    "name": "FeeRedirectUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "pairAmount",
        "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": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "feeWallet",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "positionManager",
        "type": "address"
      }
    ],
    "name": "PositionRegistered",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "claimFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "pairAmount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRouter",
    "outputs": [
      {
        "internalType": "contract FeeRouter",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "feeWalletOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getPosition",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint256",
            "name": "positionId",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "pairToken",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "feeWallet",
            "type": "address"
          },
          {
            "internalType": "bool",
            "name": "isToken0",
            "type": "bool"
          }
        ],
        "internalType": "struct LauncherLocker.Position",
        "name": "",
        "type": "tuple"
      }
    ],
    "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": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "positionId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "positionManager",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pairToken",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeWallet",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "isToken0",
        "type": "bool"
      }
    ],
    "name": "registerPosition",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "factory_",
        "type": "address"
      }
    ],
    "name": "setFactory",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "newFeeWallet",
        "type": "address"
      }
    ],
    "name": "setFeeRedirect",
    "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.26;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {INonfungiblePositionManager} from "./interfaces/IUniswapV3.sol";
import {FeeRouter} from "./FeeRouter.sol";

/// @notice Permanently holds LP NFTs for launched tokens. There is no withdraw
/// path: liquidity is locked forever. Anyone may trigger a fee claim for a token;
/// proceeds are split by the FeeRouter between the token's feeWallet and the
/// protocol treasury.
contract LauncherLocker is Ownable {
    using SafeERC20 for IERC20;

    error NotFactory();
    error NotFeeWallet();
    error UnknownToken();
    error ZeroAddress();
    error AlreadyRegistered();

    event PositionRegistered(
        address indexed token, uint256 indexed positionId, address indexed feeWallet, address positionManager
    );
    event FeesClaimed(address indexed token, uint256 indexed positionId, uint256 tokenAmount, uint256 pairAmount);
    event FeeRedirectUpdated(address indexed token, address indexed newFeeWallet);
    event FactoryUpdated(address factory);

    struct Position {
        uint256 positionId;
        address positionManager;
        address pairToken;
        address feeWallet;
        bool isToken0; // launched token is token0 of the pool
    }

    FeeRouter public immutable feeRouter;
    address public factory;

    mapping(address token => Position) internal positions;

    constructor(address payable feeRouter_) Ownable(msg.sender) {
        if (feeRouter_ == address(0)) revert ZeroAddress();
        feeRouter = FeeRouter(feeRouter_);
    }

    /// @notice One-time wiring of the factory (set at deployment).
    function setFactory(address factory_) external onlyOwner {
        if (factory_ == address(0)) revert ZeroAddress();
        factory = factory_;
        emit FactoryUpdated(factory_);
    }

    /// @notice Called by the factory right after minting the LP NFT to this contract.
    function registerPosition(
        address token,
        uint256 positionId,
        address positionManager,
        address pairToken,
        address feeWallet,
        bool isToken0
    ) external {
        if (msg.sender != factory) revert NotFactory();
        if (positions[token].positionManager != address(0)) revert AlreadyRegistered();
        positions[token] = Position({
            positionId: positionId,
            positionManager: positionManager,
            pairToken: pairToken,
            feeWallet: feeWallet,
            isToken0: isToken0
        });
        emit PositionRegistered(token, positionId, feeWallet, positionManager);
    }

    /// @notice Current fee recipient can redirect future fee payouts.
    function setFeeRedirect(address token, address newFeeWallet) external {
        Position storage pos = positions[token];
        if (pos.positionManager == address(0)) revert UnknownToken();
        if (msg.sender != pos.feeWallet) revert NotFeeWallet();
        if (newFeeWallet == address(0)) revert ZeroAddress();
        pos.feeWallet = newFeeWallet;
        emit FeeRedirectUpdated(token, newFeeWallet);
    }

    /// @notice Collect accrued LP fees for `token` and distribute via the FeeRouter.
    /// Callable by anyone; funds always flow to feeWallet + treasury.
    function claimFees(address token) external returns (uint256 tokenAmount, uint256 pairAmount) {
        Position memory pos = positions[token];
        if (pos.positionManager == address(0)) revert UnknownToken();

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

        (tokenAmount, pairAmount) = pos.isToken0 ? (amount0, amount1) : (amount1, amount0);
        feeRouter.distribute(token, pos.pairToken, tokenAmount, pairAmount, pos.feeWallet);
        emit FeesClaimed(token, pos.positionId, tokenAmount, pairAmount);
    }

    /// @notice Pending (uncollected) fees are read off-chain via positionManager.positions().
    function getPosition(address token) external view returns (Position memory) {
        Position memory pos = positions[token];
        if (pos.positionManager == address(0)) revert UnknownToken();
        return pos;
    }

    function feeWalletOf(address token) external view returns (address) {
        Position memory pos = positions[token];
        if (pos.positionManager == address(0)) revert UnknownToken();
        return pos.feeWallet;
    }

    /// @notice Accept LP NFTs minted directly to this contract.
    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }
}
Chain explorer2206msChain node78ms