JellyfishLiquidityVault

0xa4fb977d00ede9f25741239796c22b81f2380888

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
2,363 bytes
ABI entries
29
5 read · 8 write
License
none

Contract information

Address
0xa4fb977d00ede9f25741239796c22b81f2380888
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x84607ecc21…27e1abc3cf

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (5)

owner()address
pendingOwner()address
positionManager()address
positionRecorded()bool
positionTokenId()uint256

Events (6)

NativeRescuedOwnershipTransferStartedOwnershipTransferredPositionRecordedPositionTransferredUnsupportedTokenRescued

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "safeOwner",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "InvalidRecipient",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NativeTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyPositionManager",
    "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": "PositionAlreadyRecorded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PositionNotRecorded",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "NativeRescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "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": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "PositionRecorded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "PositionTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "UnsupportedTokenRescued",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "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": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionRecorded",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "recordPosition",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address payable",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueNative",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IERC20",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueUnsupportedToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "transferPosition",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

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

/// @notice Safe-owned custody for the official PositionManager LP NFT.
/// @dev Nothing burns or removes liquidity automatically. Moving the LP NFT
/// requires an explicit transaction from the approved Safe owner.
contract JellyfishLiquidityVault is IERC721Receiver, Ownable2Step {
    using SafeERC20 for IERC20;

    address public immutable positionManager;
    uint256 public positionTokenId;
    bool public positionRecorded;

    event PositionRecorded(uint256 indexed tokenId);
    event PositionTransferred(uint256 indexed tokenId, address indexed recipient);
    event UnsupportedTokenRescued(address indexed token, address indexed recipient, uint256 amount);
    event NativeRescued(address indexed recipient, uint256 amount);

    error OnlyPositionManager();
    error PositionAlreadyRecorded();
    error PositionNotRecorded();
    error InvalidRecipient();
    error NativeTransferFailed();

    constructor(address positionManager_, address safeOwner) Ownable(safeOwner) {
        if (positionManager_.code.length == 0 || safeOwner == address(0)) {
            revert InvalidRecipient();
        }
        positionManager = positionManager_;
    }

    receive() external payable { }

    function onERC721Received(address, address, uint256 tokenId, bytes calldata)
        external
        returns (bytes4)
    {
        if (msg.sender != positionManager) revert OnlyPositionManager();
        _recordPosition(tokenId);
        return IERC721Receiver.onERC721Received.selector;
    }

    function recordPosition(uint256 tokenId) external {
        if (IERC721(positionManager).ownerOf(tokenId) != address(this)) {
            revert OnlyPositionManager();
        }
        _recordPosition(tokenId);
    }

    function transferPosition(address recipient) external onlyOwner {
        if (!positionRecorded) revert PositionNotRecorded();
        if (recipient == address(0)) revert InvalidRecipient();
        uint256 tokenId = positionTokenId;
        positionRecorded = false;
        IERC721(positionManager).safeTransferFrom(address(this), recipient, tokenId);
        emit PositionTransferred(tokenId, recipient);
    }

    function rescueUnsupportedToken(IERC20 token, address recipient, uint256 amount)
        external
        onlyOwner
    {
        if (recipient == address(0)) revert InvalidRecipient();
        token.safeTransfer(recipient, amount);
        emit UnsupportedTokenRescued(address(token), recipient, amount);
    }

    function rescueNative(address payable recipient, uint256 amount) external onlyOwner {
        if (recipient == address(0)) revert InvalidRecipient();
        (bool success,) = recipient.call{ value: amount }("");
        if (!success) revert NativeTransferFailed();
        emit NativeRescued(recipient, amount);
    }

    function _recordPosition(uint256 tokenId) private {
        if (positionRecorded) revert PositionAlreadyRecorded();
        positionTokenId = tokenId;
        positionRecorded = true;
        emit PositionRecorded(tokenId);
    }
}
Chain explorer3577msChain node83ms