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
- Creator
- 0xc14C897c6b…9230205125
- 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