HookSwapTokenLockerManager

0x35db40f22143651159056285e92c113ece65e7e2

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
10,983 bytes
ABI entries
21
9 read · 6 write
License
none

Contract information

Address
0x35db40f22143651159056285e92c113ece65e7e2
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xec0c31df16…55de8fffe7

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (9)

creationEnabled()bool
feeReceiver()address
getLpData(uint40)bool, uint40, address, address, uint256, uint256, uint256, uint256
getTokenLockAddress(uint40)address
getTokenLockData(uint40)bool, uint40, address, address, address, address, uint40, uint40, uint256, uint256
getTokenLockersForAddress(address)uint40[]
lockFee()uint256
owner()address
tokenLockerCount()uint40

Events (5)

FeeReceiverUpdatedLockFeePaidLockFeeUpdatedOwnershipTransferredTokenLockerCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "lockFee_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "feeReceiver_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "oldReceiver",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "newReceiver",
        "type": "address"
      }
    ],
    "name": "FeeReceiverUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "payer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "LockFeePaid",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "oldFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "LockFeeUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint40",
        "name": "id",
        "type": "uint40"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token0",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token1",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "createdBy",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint40",
        "name": "unlockTime",
        "type": "uint40"
      }
    ],
    "name": "TokenLockerCreated",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount_",
        "type": "uint256"
      },
      {
        "internalType": "uint40",
        "name": "unlockTime_",
        "type": "uint40"
      }
    ],
    "name": "createTokenLocker",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creationEnabled",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeReceiver",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint40",
        "name": "id_",
        "type": "uint40"
      }
    ],
    "name": "getLpData",
    "outputs": [
      {
        "internalType": "bool",
        "name": "hasLpData",
        "type": "bool"
      },
      {
        "internalType": "uint40",
        "name": "id",
        "type": "uint40"
      },
      {
        "internalType": "address",
        "name": "token0",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token1",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "balance1",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "price0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "price1",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint40",
        "name": "id_",
        "type": "uint40"
      }
    ],
    "name": "getTokenLockAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint40",
        "name": "id_",
        "type": "uint40"
      }
    ],
    "name": "getTokenLockData",
    "outputs": [
      {
        "internalType": "bool",
        "name": "isLpToken",
        "type": "bool"
      },
      {
        "internalType": "uint40",
        "name": "id",
        "type": "uint40"
      },
      {
        "internalType": "address",
        "name": "contractAddress",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "lockOwner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "createdBy",
        "type": "address"
      },
      {
        "internalType": "uint40",
        "name": "createdAt",
        "type": "uint40"
      },
      {
        "internalType": "uint40",
        "name": "unlockTime",
        "type": "uint40"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "totalSupply",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "address_",
        "type": "address"
      }
    ],
    "name": "getTokenLockersForAddress",
    "outputs": [
      {
        "internalType": "uint40[]",
        "name": "",
        "type": "uint40[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lockFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint40",
        "name": "id_",
        "type": "uint40"
      },
      {
        "internalType": "address",
        "name": "newOwner_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "previousOwner_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "createdBy_",
        "type": "address"
      }
    ],
    "name": "notifyLockerOwnerChange",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bool",
        "name": "value_",
        "type": "bool"
      }
    ],
    "name": "setCreationEnabled",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "feeReceiver_",
        "type": "address"
      }
    ],
    "name": "setFeeReceiver",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "lockFee_",
        "type": "uint256"
      }
    ],
    "name": "setLockFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenLockerCount",
    "outputs": [
      {
        "internalType": "uint40",
        "name": "",
        "type": "uint40"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner_",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

/**
 * HookSwap Token & LP Locker — manager (v2/v2-LP + ERC-20).
 *
 * Based on the SourceHat-audited OnlyMoons `TokenLockerManagerV1`
 * (contracts/locker/onlymoons/, GPL-3.0), extended with a configurable
 * native-token LOCK FEE. Everything else — the per-lock `HookSwapTokenLocker` child,
 * the owner search index, LP detection — is the audited behavior, unchanged.
 *
 * Scope: locks ERC-20 tokens and Uniswap-V2 LP tokens. v3 concentrated-liquidity
 * positions are ERC-721 NFTs and are handled by `HookSwapV3PositionLocker.sol`.
 *
 * NOTE: adding fees changes the audited bytecode — have this reviewed before
 * mainnet use with real value.
 */

import { Ownable } from "./Ownable.sol";
import { IERC20 } from "./library/IERC20.sol";
import { HookSwapTokenLocker } from "./HookSwapTokenLocker.sol";

// Not `is ITokenLockerManagerV1`: that interface declares createTokenLocker as
// non-payable and Solidity forbids overriding it as payable. This manager exposes
// the same functions (so ABIs/frontends match) and satisfies the only call the
// child makes back into it — notifyLockerOwnerChange.
contract HookSwapTokenLockerManager is Ownable {
  event TokenLockerCreated(
    uint40 id,
    address indexed token,
    address indexed token0,
    address indexed token1,
    address createdBy,
    uint256 balance,
    uint40 unlockTime
  );
  event LockFeeUpdated(uint256 oldFee, uint256 newFee);
  event FeeReceiverUpdated(address oldReceiver, address newReceiver);
  event LockFeePaid(address indexed payer, uint256 amount);

  constructor(uint256 lockFee_, address feeReceiver_) Ownable(_msgSender()) {
    _creationEnabled = true;
    _lockFee = lockFee_;
    // default the fee receiver to the deployer when not provided
    _feeReceiver = feeReceiver_ == address(0) ? _msgSender() : feeReceiver_;
  }

  bool private _creationEnabled;
  uint40 private _tokenLockerCount;

  /** @dev native-token fee required to create a locker (0 = free). */
  uint256 private _lockFee;
  /** @dev where collected fees are sent. */
  address private _feeReceiver;

  mapping(uint40 => HookSwapTokenLocker) private _tokenLockers;
  mapping(address => uint40[]) private _tokenLockersForAddress;

  modifier allowCreation() {
    require(_creationEnabled, "Locker creation is disabled");
    _;
  }

  /* --------------------------------------------------------------- fees */

  function lockFee() external view returns (uint256) {
    return _lockFee;
  }

  function feeReceiver() external view returns (address) {
    return _feeReceiver;
  }

  function setLockFee(uint256 lockFee_) external onlyOwner {
    emit LockFeeUpdated(_lockFee, lockFee_);
    _lockFee = lockFee_;
  }

  function setFeeReceiver(address feeReceiver_) external onlyOwner {
    require(feeReceiver_ != address(0), "Fee receiver cannot be the zero address");
    emit FeeReceiverUpdated(_feeReceiver, feeReceiver_);
    _feeReceiver = feeReceiver_;
  }

  /** @dev collects exactly `_lockFee` in native token and forwards it to the receiver. */
  function _collectFee() private {
    uint256 fee = _lockFee;
    require(msg.value >= fee, "Insufficient lock fee");
    if (fee > 0) {
      (bool sent, ) = payable(_feeReceiver).call{ value: fee }("");
      require(sent, "Fee transfer failed");
      emit LockFeePaid(_msgSender(), fee);
    }
    // refund any overpayment
    uint256 refund = msg.value - fee;
    if (refund > 0) {
      (bool refunded, ) = payable(_msgSender()).call{ value: refund }("");
      require(refunded, "Refund failed");
    }
  }

  /* --------------------------------------------------------------- views */

  function tokenLockerCount() external view returns (uint40) {
    return _tokenLockerCount;
  }

  function creationEnabled() external view returns (bool) {
    return _creationEnabled;
  }

  function setCreationEnabled(bool value_) external onlyOwner {
    _creationEnabled = value_;
  }

  /* ------------------------------------------------------------- create */

  function createTokenLocker(
    address tokenAddress_,
    uint256 amount_,
    uint40 unlockTime_
  ) external payable allowCreation {
    _collectFee();

    uint40 id = _tokenLockerCount++;
    _tokenLockers[id] = new HookSwapTokenLocker(address(this), id, _msgSender(), tokenAddress_, unlockTime_);
    address lockerAddress = address(_tokenLockers[id]);

    IERC20 token = IERC20(tokenAddress_);
    token.transferFrom(_msgSender(), lockerAddress, amount_);

    _tokenLockersForAddress[_msgSender()].push(id);
    _tokenLockersForAddress[tokenAddress_].push(id);
    _tokenLockersForAddress[lockerAddress].push(id);

    (bool hasLpData, , address token0Address, address token1Address, , , , ) = _tokenLockers[id].getLpData();

    if (hasLpData) {
      _tokenLockersForAddress[token0Address].push(id);
      _tokenLockersForAddress[token1Address].push(id);
    }

    emit TokenLockerCreated(
      id,
      tokenAddress_,
      token0Address,
      token1Address,
      _msgSender(),
      token.balanceOf(lockerAddress),
      unlockTime_
    );
  }

  function getTokenLockAddress(uint40 id_) external view returns (address) {
    return address(_tokenLockers[id_]);
  }

  function getTokenLockData(uint40 id_) external view returns (
    bool isLpToken,
    uint40 id,
    address contractAddress,
    address lockOwner,
    address token,
    address createdBy,
    uint40 createdAt,
    uint40 unlockTime,
    uint256 balance,
    uint256 totalSupply
  ) {
    return _tokenLockers[id_].getLockData();
  }

  function getLpData(uint40 id_) external view returns (
    bool hasLpData,
    uint40 id,
    address token0,
    address token1,
    uint256 balance0,
    uint256 balance1,
    uint256 price0,
    uint256 price1
  ) {
    return _tokenLockers[id_].getLpData();
  }

  function getTokenLockersForAddress(address address_) external view returns (uint40[] memory) {
    return _tokenLockersForAddress[address_];
  }

  function notifyLockerOwnerChange(
    uint40 id_,
    address newOwner_,
    address previousOwner_,
    address createdBy_
  ) external {
    require(_msgSender() == address(_tokenLockers[id_]), "Only the locker contract can call this function");

    if (previousOwner_ != createdBy_) {
      for (uint256 i = 0; i < _tokenLockersForAddress[previousOwner_].length; i++) {
        if (_tokenLockersForAddress[previousOwner_][i] != id_) continue;
        _tokenLockersForAddress[previousOwner_][i] = _tokenLockersForAddress[previousOwner_][
          _tokenLockersForAddress[previousOwner_].length - 1
        ];
        _tokenLockersForAddress[previousOwner_].pop();
        break;
      }
    }

    bool hasId = false;
    for (uint256 i = 0; i < _tokenLockersForAddress[newOwner_].length; i++) {
      if (_tokenLockersForAddress[newOwner_][i] == id_) {
        hasId = true;
        break;
      }
    }

    if (!hasId) {
      _tokenLockersForAddress[newOwner_].push(id_);
    }
  }
}
Chain explorer659msChain node75ms