TokenLocker
0xfe52aa897703dca37b76b6fc51b12dc6f9f442c0
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
2,816 bytes
ABI entries
15
7 read · 3 write
License
none
Contract information
- Address
- 0xfe52aa897703dca37b76b6fc51b12dc6f9f442c0
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xEDC32d6257…3F35DfC9d0
- Creation tx
- 0x53080b843e…7e31cbbf80
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (7)
lockCount() → uint256
lockedBalance(address, address) → uint256
locks(uint256) → address, address, uint256, uint64, bool
locksByToken(address, uint256) → uint256
locksByTokenCount(address) → uint256
locksOf(address, uint256) → uint256
locksOfCount(address) → uint256
Events (3)
ExtendedLockedWithdrawn
ABI
[
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint64",
"name": "unlockAt",
"type": "uint64"
}
],
"name": "Extended",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint64",
"name": "unlockAt",
"type": "uint64"
}
],
"name": "Locked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdrawn",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "newUnlockAt",
"type": "uint64"
}
],
"name": "extend",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "unlockAt",
"type": "uint64"
},
{
"internalType": "address",
"name": "beneficiary",
"type": "address"
}
],
"name": "lock",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "lockCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "owner_",
"type": "address"
}
],
"name": "lockedBalance",
"outputs": [
{
"internalType": "uint256",
"name": "sum",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "locks",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "unlockAt",
"type": "uint64"
},
{
"internalType": "bool",
"name": "withdrawn",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "locksByToken",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "locksByTokenCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "locksOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
}
],
"name": "locksOfCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FLOW — TokenLocker (contract #5a) ⚠️ WIP, NOT AUDITED
A generic, public, EXTEND-ONLY token time-lock. Anyone can lock any ERC-20 for a beneficiary until an
unlock timestamp that can only ever be pushed LATER (never shortened), then the beneficiary withdraws.
Primary use: the dev first-buy lock — a creator buys their own token at launch and locks it here, a
visible, trustless "skin in the game / can't rug the buy" signal (the frontend bundles buy → lock).
Fee-on-transfer safe: the lock records the amount actually RECEIVED, so nothing is over-credited.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract TokenLocker is ReentrancyGuard {
using SafeERC20 for IERC20;
struct Lock {
address token;
address owner; // the beneficiary who may extend + withdraw
uint256 amount; // amount actually received (fee-on-transfer safe)
uint64 unlockAt;
bool withdrawn;
}
Lock[] public locks;
mapping(address => uint256[]) public locksOf; // beneficiary → lock ids
mapping(address => uint256[]) public locksByToken; // token → lock ids
event Locked(uint256 indexed id, address indexed token, address indexed owner, uint256 amount, uint64 unlockAt);
event Extended(uint256 indexed id, uint64 unlockAt);
event Withdrawn(uint256 indexed id, uint256 amount);
/// Lock `amount` of `token` for `beneficiary` until `unlockAt`. Pulls tokens from msg.sender.
function lock(address token, uint256 amount, uint64 unlockAt, address beneficiary)
external nonReentrant returns (uint256 id)
{
require(token != address(0) && beneficiary != address(0), "zero");
require(amount > 0, "amount");
require(unlockAt > block.timestamp, "past");
IERC20 t = IERC20(token);
uint256 before = t.balanceOf(address(this));
t.safeTransferFrom(msg.sender, address(this), amount);
uint256 received = t.balanceOf(address(this)) - before;
require(received > 0, "no receipt");
id = locks.length;
locks.push(Lock(token, beneficiary, received, unlockAt, false));
locksOf[beneficiary].push(id);
locksByToken[token].push(id);
emit Locked(id, token, beneficiary, received, unlockAt);
}
/// Push the unlock time later. Extend-only — can never shorten a lock.
function extend(uint256 id, uint64 newUnlockAt) external {
Lock storage l = locks[id];
require(msg.sender == l.owner, "not owner");
require(!l.withdrawn, "withdrawn");
require(newUnlockAt > l.unlockAt, "extend only");
l.unlockAt = newUnlockAt;
emit Extended(id, newUnlockAt);
}
/// Withdraw a matured lock to its beneficiary.
function withdraw(uint256 id) external nonReentrant {
Lock storage l = locks[id];
require(msg.sender == l.owner, "not owner");
require(!l.withdrawn, "withdrawn");
require(block.timestamp >= l.unlockAt, "locked");
l.withdrawn = true;
IERC20(l.token).safeTransfer(l.owner, l.amount);
emit Withdrawn(id, l.amount);
}
// ─────────────────────────── views ───────────────────────────
function lockCount() external view returns (uint256) { return locks.length; }
function locksOfCount(address owner_) external view returns (uint256) { return locksOf[owner_].length; }
function locksByTokenCount(address token) external view returns (uint256) { return locksByToken[token].length; }
/// Sum of still-locked (not yet withdrawn) `token` held for `owner_` — for a "dev locked N" UI badge.
function lockedBalance(address token, address owner_) external view returns (uint256 sum) {
uint256[] storage ids = locksOf[owner_];
for (uint256 i = 0; i < ids.length; i++) {
Lock storage l = locks[ids[i]];
if (l.token == token && !l.withdrawn) sum += l.amount;
}
}
}
Chain explorer1643msChain node92ms