LpLocker

0x5bd90376975d7bbbd73ecef1d127955c0af4931c

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
1,107 bytes
ABI entries
8
3 read · 2 write
License
none

Contract information

Address
0x5bd90376975d7bbbd73ecef1d127955c0af4931c
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x353b8802f9…badcb1af91

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (3)

feeRecipient()address
positionManager()address
tokenId()uint256

Events (2)

FeesCollectedLocked

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_positionManager",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_feeRecipient",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "name": "FeesCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "Locked",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "collectFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "_id",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {INonfungiblePositionManager} from "./interfaces/INonfungiblePositionManager.sol";

interface IERC721Receiver {
    function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4);
}

/// @title LpLocker
/// @notice Holds a Uniswap v3 LP NFT forever.
///         Only allows fees to be collected to an immutable recipient.
///         The position can NEVER be unwound: there is no path to
///         decreaseLiquidity, transferFrom, or burn.
///         From a withdrawal standpoint this is equivalent to burning the LP,
///         while still allowing trading fees to be claimed.
contract LpLocker is IERC721Receiver {
    INonfungiblePositionManager public immutable positionManager;
    address public immutable feeRecipient;
    uint256 public tokenId;

    event Locked(uint256 indexed tokenId);
    event FeesCollected(uint256 amount0, uint256 amount1);

    constructor(address _positionManager, address _feeRecipient) {
        require(_positionManager != address(0), "pm=0");
        require(_feeRecipient != address(0), "recipient=0");
        positionManager = INonfungiblePositionManager(_positionManager);
        feeRecipient = _feeRecipient;
    }

    /// @notice Receives the LP NFT exactly once.
    function onERC721Received(address, address, uint256 _id, bytes calldata)
        external
        returns (bytes4)
    {
        require(msg.sender == address(positionManager), "not v3 NFT");
        require(tokenId == 0, "already locked");
        tokenId = _id;
        emit Locked(_id);
        return IERC721Receiver.onERC721Received.selector;
    }

    /// @notice Anyone may call. Fees always go to the immutable recipient.
    function collectFees() external returns (uint256 amount0, uint256 amount1) {
        (amount0, amount1) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: tokenId,
                recipient: feeRecipient,
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );
        emit FeesCollected(amount0, amount1);
    }
}
Chain explorer3072msChain node92ms