Chuyển tới nội dung chính

Secure Lock

Secure Lock là locker on-chain của CheesePad cho liquidity pool, token allocation và vesting stream.
Nó kết hợp lock explorer công khai, minh bạch với các luồng hướng dẫn để team cấu hình lock đúng, đồng thời dịch vụ bên thứ ba có thể index và hiển thị ổn định.

Trang này tập trung vào data model, behavior và quy ước hiển thị để:

  • Explorer / tool bên thứ ba (ví dụ: DEX analytics, tracker) có thể ingest và hiển thị thông tin Secure Lock nhất quán.
  • Developers có thể hiểu lock hoạt động như thế nào theo thời gian.
  • Người dùng cuối có thể nhanh chóng hiểu đang khóa gì, trong bao lâu và điều kiện nào để mở khóa.

Lưu ý: Đây là trang tham chiếu kỹ thuật. Tên struct/hàm và ví dụ code được giữ nguyên bằng tiếng Anh để tránh nhầm lẫn.

Để xem source code contract, ABI và script deploy, xem repo mã nguồn mở:
https://github.com/cheesepad-ai/cheese-lock.

Trang lock chính thức của CheesePad (BSC):

CheeseLock contract đã deploy

CheeseLock là smart contract on-chain được CheesePad dùng để khóa token an toàn và vesting.

BNB Smart Chain

Địa chỉ contract CheeseLock:

0x1E7826b7E3244aDB5e137A5F1CE49095f6857140

Xem trên BscScan

BNB Smart Chain Testnet

Địa chỉ contract CheeseLock:

0x1E7826b7E3244aDB5e137A5F1CE49095f6857140

Xem trên BscScan (testnet)

Contract & network on-chain

Secure Lock được triển khai dưới dạng locker contract on-chain (được gọi trong tài liệu này là locker contract).

  • Tên contract: thường là SecureLock (xem repo cheese-lock để biết chính xác tên và ABI).
  • Network: component chain agnostic, nhưng deployment production hiện tập trung vào EVM chains.
    Trích tài liệu sản phẩm: “BSC is live today, but the component is chain agnostic. Use uppercase network tickers.”
  • Địa chỉ chính thức: địa chỉ locker contract cho mỗi chain được hỗ trợ được duy trì trong repo cheese-lock và cấu hình deploy nội bộ của CheesePad.

Khi tích hợp:

  • Luôn lấy địa chỉ locker chính thức từ:
    • Repo GitHub cheese-lock, hoặc
    • Docs / thông báo chính thức của CheesePad, hoặc
    • Xác nhận trực tiếp từ core team CheesePad.
  • Không dựa vào bản sao địa chỉ contract từ bên thứ ba.
  • Hãy xác minh rằng:
    • Bytecode khớp với source đã publish trên block explorer tương ứng.
    • Địa chỉ thuộc quyền sở hữu và được công bố bởi team CheesePad chính thức.

Explorer bên thứ ba (ví dụ: DEX tools) thường sẽ:

  • Theo dõi event của locker contract để bắt lock creation và unlock activity.
  • Map mỗi bản ghi on-chain vào Secure Lock data model bên dưới.
  • Render lock card và chi tiết bằng các trường status, scheduleamount.

Các loại lock được hỗ trợ

Ở tầng Solidity, contract CheeseLock chỉ expose một Lock struct.
Các “loại lock” khác nhau được biểu diễn bằng tổ hợp các field và hàm tạo, chứ không phải enum on-chain.

  • Asset type (token vs LP):
    • Được quyết định bởi cờ isLpToken truyền vào lock, vestingLock hoặc multipleVestingLock.
    • Nội bộ: LP lock được đăng ký qua _lockLpToken, còn non-LP lock qua _lockNormalToken.
    • Với LP lock, cumulativeLockInfo[token].factory được set thành factory của LP; với token thường là address(0).
  • Schedule type (normal vs vesting):
    • Normal lock: tạo qua lock(...), lưu dưới dạng Lock với tgeBps == 0, cycle == 0, cycleBps == 0. Nó mở một lần tại tgeDate.
    • Vesting lock: tạo qua vestingLock(...) hoặc multipleVestingLock(...), lưu dưới dạng Lock với tgeBps > 0, cycle > 0, cycleBps > 0tgeBps + cycleBps <= 10_000. Nó mở dần theo thời gian.

Ngoài ra, tầng product/UI có thể tự do gắn nhãn lock như “LP Lock”, “Team allocation”, ... nhưng các nhãn này không được encode trong Solidity contract.

Khái niệm cốt lõi

Ở mức cao, một Secure Lock instance sẽ có:

  • Lock identity: định danh duy nhất toàn cục và giao dịch on-chain đã tạo lock.
  • Scope: token hoặc cặp LP nào đang bị khóa và trên chain nào.
  • Amount: số token (hoặc LP token) bị khóa và (tuỳ chọn) tham chiếu USD.
  • Unlock schedule: khi nào và bằng cách nào vị thế bị khóa trở nên có thể claim.
  • Status: lock đang active, mở một phần, hoàn tất hoàn toàn hay bị hủy (nếu áp dụng).

Tài liệu sản phẩm nhấn mạnh:

  • Minh bạch: trang lock explorer công khai để bất kỳ ai cũng có thể kiểm chứng lock của dự án.
  • Nhất quán: chip và badge UI truyền đạt rõ schedule và loại tài sản. Chúng được triển khai ở tầng product/indexer và không thuộc Solidity interface.

Mô hình dữ liệu on-chain

Phần này ghi lại các Solidity struct thực tế và hành vi quan sát được của contract CheeseLock.
Mọi nội dung bên dưới được trích trực tiếp từ code contract trong cheesepad-ai/cheese-lock.

1. Lock struct

Each lock is stored in an array:

struct Lock {
uint256 id;
address token;
address owner;
uint256 amount;
uint256 lockDate;
uint256 tgeDate; // TGE date for vesting locks, unlock date for normal locks
uint256 tgeBps; // In bips. Is 0 for normal locks
uint256 cycle; // Is 0 for normal locks
uint256 cycleBps; // In bips. Is 0 for normal locks
uint256 unlockedAmount;
string description;
}

Field semantics:

  • id: Lock identifier. In this implementation it is locks.length + ID_PADDING at creation time.
  • token: ERC‑20 token address being locked. For LP locks this is the LP token address.
  • owner: Current owner of the lock; has permission to unlock, edit, or transfer ownership.
  • amount: Total amount of tokens associated with this lock (in token smallest units).
  • lockDate: Block timestamp when the lock was created.
  • tgeDate:
    • For normal locks (tgeBps == 0 and cycle == 0 and cycleBps == 0): the unlock timestamp.
    • For vesting locks: the TGE (Token Generation Event) timestamp when the first portion becomes claimable.
  • tgeBps:
    • 0 for normal locks.
    • For vesting locks: basis points (out of 10_000) that unlock at tgeDate.
  • cycle:
    • 0 for normal locks.
    • For vesting locks: cycle duration in seconds.
  • cycleBps:
    • 0 for normal locks.
    • For vesting locks: basis points of amount that unlock on each cycle after TGE.
  • unlockedAmount: Total amount already withdrawn by the owner.
  • description: Free-form text; editable via editLockDescription.

Normal vs vesting locks:

  • lock(...) creates a normal lock:
    • tgeBps = 0, cycle = 0, cycleBps = 0.
    • tgeDate is the single unlock timestamp.
  • vestingLock(...) and multipleVestingLock(...) create vesting locks:
    • Require tgeDate > now, cycle > 0, 0 < tgeBps < 10_000, 0 < cycleBps < 10_000, and tgeBps + cycleBps <= 10_000.

2. CumulativeLockInfo struct

The contract tracks cumulative locked amounts per token:

struct CumulativeLockInfo {
address token;
address factory;
uint256 amount;
}
  • token: The ERC‑20 / LP token address.
  • factory:
    • For LP locks: the LP’s factory (e.g. UniswapV2‑compatible factory), inferred via _parseFactoryAddress.
    • For normal token locks: address(0).
  • amount: Sum of amount minus unlockedAmount over all active locks for this token.

Explorers can use factory != address(0) as a canonical way to detect LP tokens for this locker.

3. Internal sets and indexes

Internally the contract organizes locks using EnumerableSet:

  • By user & asset type:
    • _userLpLockIds[owner] – set of LP lock IDs for a given owner.
    • _userNormalLockIds[owner] – set of non‑LP lock IDs for a given owner.
  • By token type:
    • _lpLockedTokens – set of LP token addresses with non‑zero cumulative locked amount.
    • _normalLockedTokens – set of non‑LP token addresses with non‑zero cumulative locked amount.
  • By token:
    • _tokenToLockIds[token] – set of lock IDs associated with a given token.

These sets are surfaced via various view functions documented below, and are crucial for indexers.

4. Events

The main events emitted by CheeseLock are:

event LockAdded(
uint256 indexed id,
address token,
address owner,
uint256 amount,
uint256 unlockDate
);

event LockUpdated(
uint256 indexed id,
address token,
address owner,
uint256 newAmount,
uint256 newUnlockDate
);

event LockRemoved(
uint256 indexed id,
address token,
address owner,
uint256 amount,
uint256 unlockedAt
);

event LockVested(
uint256 indexed id,
address token,
address owner,
uint256 amount,
uint256 remaining,
uint256 timestamp
);

event LockDescriptionChanged(uint256 lockId);

event LockOwnerChanged(uint256 lockId, address owner, address newOwner);

Indexers SHOULD:

  • Subscribe to LockAdded to detect new locks (both normal and vesting).
  • Listen to LockRemoved and LockVested to track unlocks and remaining balances.
  • Use LockUpdated to pick up changes to amount or tgeDate.
  • Use LockDescriptionChanged to refresh any cached human‑readable description.
  • Use LockOwnerChanged to track ownership transfers, including renounces (newOwner = address(0)).

5. Unlock behavior

Unlocking logic is split between normal and vesting flows:

  • Normal unlock (_normalUnlock):
    • Requires block.timestamp >= userLock.tgeDate.
    • Requires userLock.unlockedAmount == 0 (one‑time full unlock).
    • Transfers amount to msg.sender, sets unlockedAmount = amount, updates cumulative totals, and emits LockRemoved.
  • Vesting unlock (_vestingUnlock):
    • Uses _withdrawableTokens(userLock) to compute how many tokens are currently claimable.
    • If newTotalUnlockAmount == amount, it removes the lock ID from the relevant sets and emits both LockRemoved and LockVested.
    • Always emits LockVested with amount unlocked in this call, remaining amount, and timestamp.

The helper used for vesting calculations:

function _withdrawableTokens(Lock memory userLock) internal view returns (uint256) {
if (userLock.amount == 0) return 0;
if (userLock.unlockedAmount >= userLock.amount) return 0;
if (block.timestamp < userLock.tgeDate) return 0;
if (userLock.cycle == 0) return 0;

uint256 tgeReleaseAmount = FullMath.mulDiv(userLock.amount, userLock.tgeBps, 10_000);
uint256 cycleReleaseAmount = FullMath.mulDiv(userLock.amount, userLock.cycleBps, 10_000);

uint256 currentTotal = 0;
if (block.timestamp >= userLock.tgeDate) {
currentTotal =
(((block.timestamp - userLock.tgeDate) / userLock.cycle) * cycleReleaseAmount) +
tgeReleaseAmount;
}

if (currentTotal > userLock.amount) {
return userLock.amount - userLock.unlockedAmount;
} else {
return currentTotal - userLock.unlockedAmount;
}
}

Informally:

  • Before tgeDate: nothing is withdrawable.
  • At tgeDate: tgeBps of amount becomes available.
  • After each multiple of cycle seconds since tgeDate: an additional cycleBps of amount is released.
  • Total unlocked is capped at amount.

6. Public/external functions relevant for integrators

From the CheeseLock implementation (and its ICheeseLock interface), key functions are:

  • Lock creation
    • lock(owner, token, isLpToken, amount, unlockDate, description) returns (uint256 id)
      • Creates a normal lock that unlocks fully at unlockDate.
    • vestingLock(owner, token, isLpToken, amount, tgeDate, tgeBps, cycle, cycleBps, description) returns (uint256 id)
      • Creates a vesting lock with TGE and cycle‑based vesting.
    • multipleVestingLock(owners[], amounts[], token, isLpToken, tgeDate, tgeBps, cycle, cycleBps, description) returns (uint256[] ids)
      • Batch version of vestingLock for multiple owners.
  • Unlocking
    • unlock(lockId)
      • If lock is vesting (tgeBps > 0), calls _vestingUnlock.
      • Otherwise, calls _normalUnlock.
    • withdrawableTokens(lockId) view returns (uint256)
      • Convenience view returning _withdrawableTokens(getLockById(lockId)).
  • Editing metadata
    • editLock(lockId, newAmount, newUnlockDate)
      • Owner‑only. Can increase amount (not decrease) and/or push tgeDate forward.
    • editLockDescription(lockId, description)
      • Owner‑only. Updates description.
  • Ownership
    • transferLockOwnership(lockId, newOwner)
    • renounceLockOwnership(lockId) (sets owner to address(0)).

Indexing & querying:

  • getTotalLockCount() view returns (uint256)
  • getLockAt(index) view returns (Lock memory)
  • getLockById(lockId) view returns (Lock memory)
  • allLpTokenLockedCount(), allNormalTokenLockedCount()
  • getCumulativeLpTokenLockInfoAt(index), getCumulativeNormalTokenLockInfoAt(index)
  • getCumulativeLpTokenLockInfo(start, end), getCumulativeNormalTokenLockInfo(start, end)
  • totalTokenLockedCount()
  • Per‑user:
    • lpLockCountForUser(user), lpLocksForUser(user), lpLockForUserAtIndex(user, index)
    • normalLockCountForUser(user), normalLocksForUser(user), normalLockForUserAtIndex(user, index)
  • Per‑token:
    • totalLockCountForToken(token)
    • getLocksForToken(token, start, end)

These views give everything a third‑party explorer needs to reconstruct all locks and balances from on-chain state.

Ví dụ data model cho indexer (TypeScript)

Below is an example off-chain schema mirroring the Solidity structs and adding a few derived helpers.
Fields marked as “derived” are computed off-chain and are not stored in the contract.

export interface LockRecord {
id: number;
token: string;
owner: string;
amount: string; // raw token units
lockDate: number; // unix seconds
tgeDate: number; // unix seconds
tgeBps: number;
cycle: number; // seconds
cycleBps: number;
unlockedAmount: string; // raw token units
description: string;

// Derived helpers (off-chain only)
isVesting: boolean; // tgeBps > 0 && cycle > 0 && cycleBps > 0
isLpToken: boolean; // cumulativeLockInfo[token].factory != address(0)
}

export interface CumulativeLockInfoRecord {
token: string;
factory: string; // 0x0 for normal tokens
amount: string; // total locked (minus unlocked)
}

Ví dụ sử dụng

This section shows how to read lock data from the CheeseLock contract using common Ethereum libraries.

Reading lock data by ID with ethers.js

import { ethers } from "ethers";
import CheeseLockABI from "./abi/CheeseLock.json";

const provider = new ethers.JsonRpcProvider("https://bsc-dataseed1.binance.org/");

// CheeseLock contract address on BNB Smart Chain
const contractAddress = "0x1E7826b7E3244aDB5e137A5F1CE49095f6857140";
const contract = new ethers.Contract(contractAddress, CheeseLockABI, provider);

async function getLockData(lockId: number) {
try {
const lockData = await contract.getLockById(lockId);
console.log("Lock Data:", {
id: lockData.id.toString(), // Unique lock identifier
token: lockData.token, // Token contract address
owner: lockData.owner, // Lock owner address
amount: lockData.amount.toString(), // Total locked amount
lockDate: new Date(Number(lockData.lockDate) * 1000).toISOString(), // Lock creation timestamp
tgeDate: new Date(Number(lockData.tgeDate) * 1000).toISOString(), // TGE / unlock date
tgeBps: lockData.tgeBps.toString(), // TGE unlock percentage in basis points
cycle: lockData.cycle.toString(), // Vesting cycle duration in seconds
cycleBps: lockData.cycleBps.toString(), // Cycle unlock percentage in basis points
unlockedAmount: lockData.unlockedAmount.toString(), // Amount already unlocked
description: lockData.description, // Lock description
});
return lockData;
} catch (error) {
console.error("Error fetching lock data:", error);
}
}

// Example usage
getLockData(1);

Reading lock data by ID with viem

import { createPublicClient, http, getContract } from "viem";
import { bsc } from "viem/chains";
import CheeseLockABI from "./abi/CheeseLock.json";

const publicClient = createPublicClient({
chain: bsc,
transport: http(),
});

const contractAddress = "0x1E7826b7E3244aDB5e137A5F1CE49095f6857140" as const;

const contract = getContract({
address: contractAddress,
abi: CheeseLockABI,
publicClient,
});

async function getLockData(lockId: bigint) {
try {
const lockData = await contract.read.getLockById([lockId]);
console.log("Lock Data:", {
id: lockData.id.toString(), // Unique lock identifier
token: lockData.token, // Token contract address
owner: lockData.owner, // Lock owner address
amount: lockData.amount.toString(), // Total locked amount
lockDate: new Date(Number(lockData.lockDate) * 1000).toISOString(), // Lock creation timestamp
tgeDate: new Date(Number(lockData.tgeDate) * 1000).toISOString(), // TGE / unlock date
tgeBps: lockData.tgeBps.toString(), // TGE unlock percentage in basis points
cycle: lockData.cycle.toString(), // Vesting cycle duration in seconds
cycleBps: lockData.cycleBps.toString(), // Cycle unlock percentage in basis points
unlockedAmount: lockData.unlockedAmount.toString(), // Amount already unlocked
description: lockData.description, // Lock description
});
return lockData;
} catch (error) {
console.error("Error fetching lock data:", error);
}
}

// Example usage
getLockData(1n);

Example: normal lock (non-vesting)

{
"id": 1,
"token": "0xTOKEN_ADDRESS",
"owner": "0xOWNER",
"amount": "1000000000000000000000",
"lockDate": 1704067200,
"tgeDate": 1706745600,
"tgeBps": 0,
"cycle": 0,
"cycleBps": 0,
"unlockedAmount": "0",
"description": "Team lock for 30 days",
"isVesting": false,
"isLpToken": false
}

Example: vesting lock with TGE and cycles

{
"id": 2,
"token": "0xTOKEN_ADDRESS",
"owner": "0xTEAM_MULTISIG",
"amount": "5000000000000000000000000",
"lockDate": 1704067200,
"tgeDate": 1706745600,
"tgeBps": 1000,
"cycle": 2592000,
"cycleBps": 375,
"unlockedAmount": "0",
"description": "Team vesting: 10% at TGE, 3.75% per month",
"isVesting": true,
"isLpToken": false
}

Review yêu cầu lock (dành cho ops / reviewer)

The Secure Lock product documentation suggests the following checklist for manual review:

  1. Confirm the token address matches the Launchpad listing (if applicable).
  2. Ensure liquidity is routed to the locker before the public pool goes live.
  3. Store transaction hashes in the CMS so the leaderboard or analytics teams can surface them later.
  4. Reply via Telegram or official channels if any field is unclear, keeping the same support channels as cheesepad.ai’s footer links.

Third‑party services do not need to perform all of these steps, but they SHOULD at least:

  • Verify that the token address is correct and not a honeypot/impersonator.
  • Verify that the locker contract is the official CheesePad Secure Lock deployment for that chain.
  • Display a clear warning if any inconsistencies are detected.

Checklist minh bạch & hiển thị công khai (dành cho explorer / analytics)

To keep the Secure Lock experience trustworthy and visually consistent across the ecosystem, we recommend:

  • Lock cards / detail pages
    • Display lock cards on routes like /lock/<chain>/<type> using a metric grid similar to CheesePad’s homepage.
    • Show at least: token/LP name, amount, USD reference, lock type, schedule badge, and countdown timer.
  • Links and sharing
    • Include a View on explorer link (e.g. BscScan) for the lock creation transaction and token contract.
    • Include a Share button so teams can promote their proof of lock on social platforms.
  • Countdown timers
    • Show live countdown timers for active locks (time until cliff or full unlock).
    • Use brown or highlight color for active timers and slate / muted text for completed locks, matching the guidance from the official docs.
  • Badges and chips
    • Use clear chips like LP Lock, Team Allocation, Cliff, Daily, Instant, etc.
    • Match the playful chip style used on CheesePad’s own UI so users immediately recognize Secure Lock semantics.

Keeping these standards tight ensures that every Secure Lock integration feels trustworthy, predictable, and on brand, whether users are viewing it on CheesePad directly or through third‑party tools and explorers.