TitanLockerManagerV2
0x26b0654a0756dcd036d4e7215324f3d2be34d79e
Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
19,299 bytes
ABI entries
43
11 read · 11 write
License
gnu_gpl_v3
Contract information
- Address
- 0x26b0654a0756dcd036d4e7215324f3d2be34d79e
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.30+commit.73712a01
- Optimization
- Enabled
- Creator
- 0x5C773302FB…78738B02DB
- Creation tx
- 0xe09a40e30f…fd37e0fecd
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (11)
creationEnabled() → bool
ethFee() → uint256
feeReceiver() → address
getTokenLockAddress(uint40) → address
getTokenLockData(uint40) → uint8, uint40, address, address, address, uint256, address, uint40, uint40, uint256
getTokenLockersForAddress(address) → uint40[]
isExemptFromFees(address) → bool
owner() → address
positionManagerKind(address) → uint8, bool
tokenFeeBps() → uint16
tokenLockerCount() → uint40
Events (7)
FeeCollectedFeeConfigUpdatedOwnershipTransferredPositionLockerCreatedPositionManagerSetTokenLockerCreatedVestingLockerCreated
ABI
[
{
"inputs": [
{
"internalType": "address payable",
"name": "feeReceiver_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint40",
"name": "id",
"type": "uint40"
},
{
"internalType": "address",
"name": "caller",
"type": "address"
}
],
"name": "CallerIsNotLocker",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "caller",
"type": "address"
}
],
"name": "CallerNotOwner",
"type": "error"
},
{
"inputs": [],
"name": "CreationDisabled",
"type": "error"
},
{
"inputs": [],
"name": "EthTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "ExemptAccountsPayNoEth",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "expected",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "received",
"type": "uint256"
}
],
"name": "IncorrectEthFee",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPositionManagerKind",
"type": "error"
},
{
"inputs": [],
"name": "InvalidVestingSchedule",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "positionManager",
"type": "address"
}
],
"name": "PositionManagerNotAllowed",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "TokenFeeTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint40",
"name": "id",
"type": "uint40"
},
{
"indexed": false,
"internalType": "bool",
"name": "paidInEth",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "FeeCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "ethFee",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint16",
"name": "tokenFeeBps",
"type": "uint16"
},
{
"indexed": true,
"internalType": "address",
"name": "feeReceiver",
"type": "address"
}
],
"name": "FeeConfigUpdated",
"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": false,
"internalType": "uint40",
"name": "id",
"type": "uint40"
},
{
"indexed": false,
"internalType": "enum ITitanLockerManagerV2.LockKind",
"name": "kind",
"type": "uint8"
},
{
"indexed": true,
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"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": "uint40",
"name": "unlockTime",
"type": "uint40"
}
],
"name": "PositionLockerCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"indexed": false,
"internalType": "enum ITitanLockerManagerV2.LockKind",
"name": "kind",
"type": "uint8"
},
{
"indexed": false,
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "PositionManagerSet",
"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"
},
{
"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": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint40",
"name": "start",
"type": "uint40"
},
{
"indexed": false,
"internalType": "uint40",
"name": "cliff",
"type": "uint40"
},
{
"indexed": false,
"internalType": "uint40",
"name": "end",
"type": "uint40"
}
],
"name": "VestingLockerCreated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId_",
"type": "uint256"
},
{
"internalType": "uint40",
"name": "unlockTime_",
"type": "uint40"
}
],
"name": "createPositionLock",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenAddress_",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount_",
"type": "uint256"
},
{
"internalType": "uint40",
"name": "unlockTime_",
"type": "uint40"
}
],
"name": "createTokenLock",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenAddress_",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount_",
"type": "uint256"
},
{
"internalType": "uint40",
"name": "start_",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "cliff_",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "end_",
"type": "uint40"
}
],
"name": "createVestingLock",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "creationEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ethFee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"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": "getTokenLockAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint40",
"name": "id_",
"type": "uint40"
}
],
"name": "getTokenLockData",
"outputs": [
{
"internalType": "enum ITitanLockerManagerV2.LockKind",
"name": "kind",
"type": "uint8"
},
{
"internalType": "uint40",
"name": "id",
"type": "uint40"
},
{
"internalType": "address",
"name": "contractAddress",
"type": "address"
},
{
"internalType": "address",
"name": "lockOwner",
"type": "address"
},
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "createdBy",
"type": "address"
},
{
"internalType": "uint40",
"name": "createdAt",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "unlockTime",
"type": "uint40"
},
{
"internalType": "uint256",
"name": "balance",
"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": [
{
"internalType": "address",
"name": "account_",
"type": "address"
}
],
"name": "isExemptFromFees",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"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": "address",
"name": "positionManager_",
"type": "address"
}
],
"name": "positionManagerKind",
"outputs": [
{
"internalType": "enum ITitanLockerManagerV2.LockKind",
"name": "kind",
"type": "uint8"
},
{
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "value_",
"type": "bool"
}
],
"name": "setCreationEnabled",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "value_",
"type": "uint256"
}
],
"name": "setEthFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account_",
"type": "address"
},
{
"internalType": "bool",
"name": "exempt_",
"type": "bool"
}
],
"name": "setFeeExempt",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "value_",
"type": "address"
}
],
"name": "setFeeReceiver",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "enum ITitanLockerManagerV2.LockKind",
"name": "kind_",
"type": "uint8"
},
{
"internalType": "bool",
"name": "allowed_",
"type": "bool"
}
],
"name": "setPositionManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "value_",
"type": "uint16"
}
],
"name": "setTokenFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "tokenFeeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"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-or-later
/**
___________________ _ __ __ ____ ________ __ __________
/_ __/ _/_ __/ | / | / / / / / __ \/ ____/ //_// ____/ __ \
/ / / / / / / /| | / |/ / / / / / / / / / ,< / __/ / /_/ /
/ / _/ / / / / ___ |/ /| / / /___/ /_/ / /___/ /| |/ /___/ _, _/
/_/ /___/ /_/ /_/ |_/_/ |_/ /_____/\____/\____/_/ |_/_____/_/ |_|
Locker manager / factory (V2 - tokens + V2/V3/V4 LP)
*/
pragma solidity 0.8.30;
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { ITitanLockerManagerV2 } from "./ITitanLockerManagerV2.sol";
import { Ownable } from "./Ownable.sol";
import { TitanLockerV2 } from "./TitanLockerV2.sol";
/// @notice Deploys and indexes `TitanLockerV2` instances for three lock kinds -
/// plain/LP ERC20 tokens, Uniswap V3 positions, and Uniswap V4 positions - all
/// through one manager. Each lock is its own child contract, so custody is
/// isolated per lock. V3/V4 positions can only be locked against a
/// position manager the owner has allowlisted, which is both the multichain
/// mechanism and the kill-switch for un-validated integrations.
contract TitanLockerManagerV2 is ITitanLockerManagerV2, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
error CreationDisabled();
error IncorrectEthFee(uint256 expected, uint256 received);
error ExemptAccountsPayNoEth();
error TokenFeeTooHigh();
error CallerIsNotLocker(uint40 id, address caller);
error EthTransferFailed();
error PositionManagerNotAllowed(address positionManager);
error InvalidPositionManagerKind();
error InvalidVestingSchedule();
uint16 private constant BPS_DENOMINATOR = 10000;
bool private _creationEnabled = true;
uint40 private _tokenLockerCount;
mapping(uint40 => TitanLockerV2) private _tokenLockers;
/// @dev Lets the frontend search lockers by creator, locked token/position
/// manager, locker address, or either underlying paired token.
mapping(address => uint40[]) private _tokenLockersForAddress;
/// @dev Allowlist of Uniswap position managers. `_pmKind` records whether a
/// manager is V3- or V4-style; `_pmAllowed` gates whether new locks may use
/// it. A manager with `_pmAllowed == false` is rejected regardless of kind.
mapping(address => LockKind) private _pmKind;
mapping(address => bool) private _pmAllowed;
uint256 private _ethFee;
uint16 private _tokenFeeBps;
address payable private _feeReceiver;
mapping(address => bool) private _feeExempt;
constructor(address payable feeReceiver_) Ownable(_msgSender()) {
if (feeReceiver_ == address(0)) revert ZeroAddress();
_feeReceiver = feeReceiver_;
// same starting fee schedule as V1; both adjustable via the setters below.
_ethFee = 0.2 ether;
_tokenFeeBps = 500; // 5.00%
}
modifier allowCreation() {
if (!_creationEnabled) revert CreationDisabled();
_;
}
// --- config ---
function tokenLockerCount() external view override returns (uint40) {
return _tokenLockerCount;
}
function creationEnabled() external view override returns (bool) {
return _creationEnabled;
}
/// @dev Turns off new locks so users can be migrated to a future version.
/// Existing locks keep working (extend/deposit/withdraw/collect) as normal.
function setCreationEnabled(bool value_) external override onlyOwner {
_creationEnabled = value_;
}
// --- position-manager allowlist ---
function positionManagerKind(address positionManager_) external view override returns (LockKind kind, bool allowed) {
return (_pmKind[positionManager_], _pmAllowed[positionManager_]);
}
function setPositionManager(address positionManager_, LockKind kind_, bool allowed_) external override onlyOwner {
if (positionManager_ == address(0)) revert ZeroAddress();
if (kind_ != LockKind.UNIV3 && kind_ != LockKind.UNIV4) revert InvalidPositionManagerKind();
_pmKind[positionManager_] = kind_;
_pmAllowed[positionManager_] = allowed_;
emit PositionManagerSet(positionManager_, kind_, allowed_);
}
// --- ERC20 lock creation ---
function createTokenLock(
address tokenAddress_,
uint256 amount_,
uint40 unlockTime_
) external payable override allowCreation nonReentrant {
uint40 id = _tokenLockerCount++;
uint256 amountToLock = _collectFee(id, tokenAddress_, amount_);
TitanLockerV2 locker = new TitanLockerV2(
address(this), id, _msgSender(), LockKind.ERC20, tokenAddress_, 0, unlockTime_, 0, 0, 0, 0
);
_tokenLockers[id] = locker;
IERC20(tokenAddress_).safeTransferFrom(_msgSender(), address(locker), amountToLock);
_indexNewLocker(id, tokenAddress_, locker);
(, address token0, address token1) = locker.getUnderlyingTokens();
_indexUnderlying(id, token0, token1);
emit TokenLockerCreated(
id,
tokenAddress_,
token0,
token1,
_msgSender(),
IERC20(tokenAddress_).balanceOf(address(locker)),
unlockTime_
);
}
/// @dev Either takes the flat `_ethFee` (100% of `amount_` gets locked), or -
/// when no ETH is sent - deducts `_tokenFeeBps` from `amount_` in the token
/// itself. Exempt accounts pay nothing and must not send ETH.
function _collectFee(uint40 id_, address tokenAddress_, uint256 amount_) private returns (uint256 amountToLock) {
bool paidInEth = msg.value != 0;
if (_feeExempt[_msgSender()]) {
if (paidInEth) revert ExemptAccountsPayNoEth();
return amount_;
}
if (paidInEth) {
if (msg.value != _ethFee) revert IncorrectEthFee(_ethFee, msg.value);
(bool success, ) = _feeReceiver.call{value: msg.value}("");
if (!success) revert EthTransferFailed();
emit FeeCollected(id_, true, msg.value);
return amount_;
}
uint256 tokenFeeAmount = (amount_ * _tokenFeeBps) / BPS_DENOMINATOR;
amountToLock = amount_ - tokenFeeAmount;
if (tokenFeeAmount != 0) {
IERC20(tokenAddress_).safeTransferFrom(_msgSender(), _feeReceiver, tokenFeeAmount);
emit FeeCollected(id_, false, tokenFeeAmount);
}
}
// --- vesting lock creation ---
/// @dev Locks a fungible token and releases it linearly (with an optional
/// cliff) to the lock owner between `start_` and `end_`. Same creation-fee
/// options as `createTokenLock`; the vesting grant is the post-fee amount and
/// is irrevocable.
function createVestingLock(
address tokenAddress_,
uint256 amount_,
uint40 start_,
uint40 cliff_,
uint40 end_
) external payable override allowCreation nonReentrant {
if (start_ >= end_ || cliff_ < start_ || cliff_ > end_ || end_ <= uint40(block.timestamp)) {
revert InvalidVestingSchedule();
}
uint40 id = _tokenLockerCount++;
uint256 amountToLock = _collectFee(id, tokenAddress_, amount_);
// Grant is the nominal post-fee amount; `release()` clamps payouts to the
// lock's live balance, so fee-on-transfer shortfalls can never over-release.
TitanLockerV2 locker = new TitanLockerV2(
address(this), id, _msgSender(), LockKind.ERC20_VESTING, tokenAddress_, 0, end_, start_, cliff_, end_, amountToLock
);
_tokenLockers[id] = locker;
IERC20(tokenAddress_).safeTransferFrom(_msgSender(), address(locker), amountToLock);
_indexNewLocker(id, tokenAddress_, locker);
(, address token0, address token1) = locker.getUnderlyingTokens();
_indexUnderlying(id, token0, token1);
emit VestingLockerCreated(id, tokenAddress_, token0, token1, _msgSender(), amountToLock, start_, cliff_, end_);
}
// --- V3/V4 position lock creation ---
function createPositionLock(
address positionManager_,
uint256 tokenId_,
uint40 unlockTime_
) external payable override allowCreation nonReentrant {
if (!_pmAllowed[positionManager_]) revert PositionManagerNotAllowed(positionManager_);
LockKind kind = _pmKind[positionManager_];
uint40 id = _tokenLockerCount++;
_collectEthFee(id);
TitanLockerV2 locker = new TitanLockerV2(
address(this), id, _msgSender(), kind, positionManager_, tokenId_, unlockTime_, 0, 0, 0, 0
);
_tokenLockers[id] = locker;
// Pull the position NFT from the depositor into the lock. Requires the
// depositor to have approved this manager as an operator for it.
IERC721(positionManager_).safeTransferFrom(_msgSender(), address(locker), tokenId_);
_indexNewLocker(id, positionManager_, locker);
(, address token0, address token1) = locker.getUnderlyingTokens();
_indexUnderlying(id, token0, token1);
emit PositionLockerCreated(id, kind, positionManager_, tokenId_, token0, token1, _msgSender(), unlockTime_);
}
/// @dev Flat-ETH-fee path used by position locks (no in-kind option exists
/// for an NFT). Exempt accounts pay nothing and must not send ETH.
function _collectEthFee(uint40 id_) private {
if (_feeExempt[_msgSender()]) {
if (msg.value != 0) revert ExemptAccountsPayNoEth();
return;
}
if (msg.value != _ethFee) revert IncorrectEthFee(_ethFee, msg.value);
if (msg.value != 0) {
(bool success, ) = _feeReceiver.call{value: msg.value}("");
if (!success) revert EthTransferFailed();
emit FeeCollected(id_, true, msg.value);
}
}
// --- indexing ---
/// @dev Records a new locker in the creator / asset / locker-address buckets.
function _indexNewLocker(uint40 id_, address asset_, TitanLockerV2 locker_) private {
_tokenLockersForAddress[_msgSender()].push(id_);
_tokenLockersForAddress[asset_].push(id_);
_tokenLockersForAddress[address(locker_)].push(id_);
}
/// @dev Records the lock under each underlying paired token, when present.
function _indexUnderlying(uint40 id_, address token0_, address token1_) private {
if (token0_ != address(0)) _tokenLockersForAddress[token0_].push(id_);
if (token1_ != address(0)) _tokenLockersForAddress[token1_].push(id_);
}
// --- reads ---
function getTokenLockAddress(uint40 id_) external view override returns (address) {
return address(_tokenLockers[id_]);
}
function getTokenLockData(uint40 id_) external view override returns (
LockKind kind,
uint40 id,
address contractAddress,
address lockOwner,
address asset,
uint256 tokenId,
address createdBy,
uint40 createdAt,
uint40 unlockTime,
uint256 balance
) {
return _tokenLockers[id_].getLockData();
}
function getTokenLockersForAddress(address address_) external view override returns (uint40[] memory) {
return _tokenLockersForAddress[address_];
}
/// @dev Called by a `TitanLockerV2` whenever its ownership changes, so the
/// search index above stays accurate.
function notifyLockerOwnerChange(
uint40 id_,
address newOwner_,
address previousOwner_,
address createdBy_
) external override {
if (_msgSender() != address(_tokenLockers[id_])) revert CallerIsNotLocker(id_, _msgSender());
if (previousOwner_ != createdBy_) {
uint40[] storage previousOwnerLocks = _tokenLockersForAddress[previousOwner_];
for (uint256 i = 0; i < previousOwnerLocks.length; i++) {
if (previousOwnerLocks[i] != id_) continue;
previousOwnerLocks[i] = previousOwnerLocks[previousOwnerLocks.length - 1];
previousOwnerLocks.pop();
break;
}
}
uint40[] storage newOwnerLocks = _tokenLockersForAddress[newOwner_];
for (uint256 i = 0; i < newOwnerLocks.length; i++) {
if (newOwnerLocks[i] == id_) return;
}
newOwnerLocks.push(id_);
}
// --- fee configuration ---
function ethFee() external view override returns (uint256) {
return _ethFee;
}
function tokenFeeBps() external view override returns (uint16) {
return _tokenFeeBps;
}
function feeReceiver() external view override returns (address) {
return _feeReceiver;
}
function isExemptFromFees(address account_) external view override returns (bool) {
return _feeExempt[account_];
}
function setEthFee(uint256 value_) external override onlyOwner {
_ethFee = value_;
emit FeeConfigUpdated(_ethFee, _tokenFeeBps, _feeReceiver);
}
function setTokenFeeBps(uint16 value_) external override onlyOwner {
if (value_ > BPS_DENOMINATOR) revert TokenFeeTooHigh();
_tokenFeeBps = value_;
emit FeeConfigUpdated(_ethFee, _tokenFeeBps, _feeReceiver);
}
function setFeeReceiver(address payable value_) external override onlyOwner {
if (value_ == address(0)) revert ZeroAddress();
_feeReceiver = value_;
emit FeeConfigUpdated(_ethFee, _tokenFeeBps, _feeReceiver);
}
function setFeeExempt(address account_, bool exempt_) external override onlyOwner {
_feeExempt[account_] = exempt_;
}
}
Chain explorer1257msChain node90ms