LauncherLocker
0x90331a631123bd2493ba962c4304dcb49f3a5d4a
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
3,590 bytes
ABI entries
29
6 read · 6 write
License
none
Contract information
- Address
- 0x90331a631123bd2493ba962c4304dcb49f3a5d4a
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x407Fe47FA0…AB006322d8
- Creation tx
- 0x7a17db9af6…a7fb258d18
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": "AlreadyInitialized",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyRegistered",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPosition",
"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": "PositionNotHeld",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"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 {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.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";
interface IFeeRecipientSync {
function syncFeeRecipientExemptions(address token) external;
}
/// @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 among the protocol, buy-burner, and the
/// token's CTO fee vault.
contract LauncherLocker is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
error NotFactory();
error NotFeeWallet();
error UnknownToken();
error ZeroAddress();
error AlreadyRegistered();
error AlreadyInitialized();
error PositionNotHeld();
error InvalidPosition();
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();
if (factory != address(0)) revert AlreadyInitialized();
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 (token == address(0) || positionManager == address(0) || pairToken == address(0) || feeWallet == address(0))
{
revert ZeroAddress();
}
if (positions[token].positionManager != address(0)) revert AlreadyRegistered();
INonfungiblePositionManager manager = INonfungiblePositionManager(positionManager);
if (manager.ownerOf(positionId) != address(this)) revert PositionNotHeld();
(,, address token0, address token1,,,,,,,,) = manager.positions(positionId);
if (isToken0 ? (token0 != token || token1 != pairToken) : (token1 != token || token0 != pairToken)) {
revert InvalidPosition();
}
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; destinations are fixed by the router plus this token's CTO vault.
function claimFees(address token) external nonReentrant returns (uint256 tokenAmount, uint256 pairAmount) {
Position memory pos = positions[token];
if (pos.positionManager == address(0)) revert UnknownToken();
// Recipient addresses are admin-configurable. Synchronize their
// restriction exemptions before transferring fees so a rotation cannot
// make max-wallet rules block collection during the launch window.
IFeeRecipientSync(factory).syncFeeRecipientExemptions(token);
(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 explorer5343msChain node84ms