LpHolder

0x9ca167e52a86e54e49ebf813468e6f2be6a94c57

Verification
Verified
v0.8.20+commit.a1b79de6
Type
Contract
2,338 bytes
ABI entries
20
3 read · 6 write
License
none

Contract information

Address
0x9ca167e52a86e54e49ebf813468e6f2be6a94c57
Chain
Robinhood Chain (4663)
Compiler
v0.8.20+commit.a1b79de6
Optimization
Enabled
Creation tx
0x2d5ee090e1…a620369e6a

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (3)

UNISWAP_V3_NFT_MANAGER()address
feeReceiver()address
owner()address

Events (2)

FeeReceiverSetOwnershipTransferred

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_nftManager",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_feeReceiver",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "target",
        "type": "address"
      }
    ],
    "name": "AddressEmptyCode",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "AddressInsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FailedInnerCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LpHolder__ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LpHolder__ZeroLength",
    "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": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "newFeeReceiver",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "oldFeeReceiver",
        "type": "address"
      }
    ],
    "name": "FeeReceiverSet",
    "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"
  },
  {
    "inputs": [],
    "name": "UNISWAP_V3_NFT_MANAGER",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256[]",
        "name": "tokenIds",
        "type": "uint256[]"
      }
    ],
    "name": "collectFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeReceiver",
    "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": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_feeReceiver",
        "type": "address"
      }
    ],
    "name": "setFeeReceiver",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "sweep",
    "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.20;

import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
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 "./external/INonfungiblePositionManager.sol";

/**
 * @title   LP Holder
 * @notice  Permanently locks and collects fees from UniV3 LP positions
 */
contract LpHolder is ERC721Holder, Ownable {
    using SafeERC20 for IERC20;

    ////////////////////////////////////////////////////////////////////////////////
    // CONSTANTS AND IMMUTABLES
    ////////////////////////////////////////////////////////////////////////////////

    INonfungiblePositionManager public immutable UNISWAP_V3_NFT_MANAGER;

    ////////////////////////////////////////////////////////////////////////////////
    // STATE
    ////////////////////////////////////////////////////////////////////////////////

    /// @notice Receives fees collected from UniV3 LP positions
    address public feeReceiver;

    ////////////////////////////////////////////////////////////////////////////////
    // CONSTRUCTION AND INITIALIZATION
    ////////////////////////////////////////////////////////////////////////////////

    constructor(address _nftManager, address _feeReceiver) Ownable(msg.sender) {
        if (_nftManager == address(0) || _feeReceiver == address(0))
            revert LpHolder__ZeroAddress();
        UNISWAP_V3_NFT_MANAGER = INonfungiblePositionManager(_nftManager);
        feeReceiver = _feeReceiver;
    }

    ////////////////////////////////////////////////////////////////////////////////
    // FEE MANAGEMENT
    ////////////////////////////////////////////////////////////////////////////////

    /**
     * @notice  Collect fees from UniV3 LP positions
     * @param   tokenIds    Array of token IDs to collect fees from
     */
    function collectFees(uint256[] calldata tokenIds) external {
        if (tokenIds.length == 0) revert LpHolder__ZeroLength();

        INonfungiblePositionManager.CollectParams
            memory params = INonfungiblePositionManager.CollectParams({
                tokenId: 0,
                recipient: feeReceiver,
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            });
        for (uint256 i; i < tokenIds.length; ) {
            params.tokenId = tokenIds[i];
            UNISWAP_V3_NFT_MANAGER.collect(params);
            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice  Sweep tokens from the contract
     * @param   token   Token to sweep
     */
    function sweep(address token) external onlyOwner {
        IERC20(token).safeTransfer(
            feeReceiver,
            IERC20(token).balanceOf(address(this))
        );
    }

    ////////////////////////////////////////////////////////////////////////////////
    // ADMINISTRATION
    ////////////////////////////////////////////////////////////////////////////////

    /**
     * @notice  Set the fee receiver
     * @param   _feeReceiver    New fee receiver
     */
    function setFeeReceiver(address _feeReceiver) external onlyOwner {
        if (_feeReceiver == address(0)) revert LpHolder__ZeroAddress();
        emit FeeReceiverSet(_feeReceiver, feeReceiver);
        feeReceiver = _feeReceiver;
    }

    ////////////////////////////////////////////////////////////////////////////////
    // EVENTS
    ////////////////////////////////////////////////////////////////////////////////

    event FeeReceiverSet(address newFeeReceiver, address oldFeeReceiver);

    ////////////////////////////////////////////////////////////////////////////////
    // ERRORS
    ////////////////////////////////////////////////////////////////////////////////

    error LpHolder__ZeroAddress();
    error LpHolder__ZeroLength();
}
Chain explorer3143msChain node77ms