Secure Lock
Secure Lock هو خزانة قفل على السلسلة من CheesePad لتجمعات السيولة وتخصيصات التوكن وجداول الاستحقاق (vesting).
يجمع بين مستكشف أقفال عام وشفاف وبين تدفقات إرشادية كي تهيئ الفرق الأقفال بشكل صحيح، بينما تستطيع الخدمات الخارجية فهرستها وعرضها بشكل موثوق.
تركّز هذه الصفحة على نموذج البيانات والسلوك ومعايير العرض لكي:
- تتمكن المستكشفات/أدوات الطرف الثالث (مثل تحليلات DEX والمتتبعات) من استيعاب معلومات Secure Lock وعرضها بشكل متسق.
- يتمكن المطورون من فهم سلوك الأقفال مع مرور الوقت.
- يتمكن المستخدمون النهائيون من فهم ما الذي تم قفله ومدة القفل وشروط فك القفل بسرعة.
ملاحظة: هذه صفحة مرجعية تقنية. تُترك أسماء البُنى/الدوال وأمثلة الكود باللغة الإنجليزية لتجنب الالتباس.
للاطلاع على كود العقد و ABI وسكربتات النشر، راجع المستودع مفتوح المصدر:
https://github.com/cheesepad-ai/cheese-lock.
صفحات القفل الرسمية على CheesePad (BSC):
- Liquidity locks: https://www.cheesepad.ai/lock/bsc/liquidity
- Token locks: https://www.cheesepad.ai/lock/bsc/token
- Create new lock: https://www.cheesepad.ai/lock/create
عقود CheeseLock المُنَشَّرة
CheeseLock هو العقد الذكي على السلسلة الذي تستخدمه CheesePad لقفل التوكن بأمان وتنفيذ الاستحقاق.
BNB Smart Chain
عنوان عقد CheeseLock:
0x1E7826b7E3244aDB5e137A5F1CE49095f6857140
BNB Smart Chain Testnet
عنوان عقد CheeseLock:
0x1E7826b7E3244aDB5e137A5F1CE49095f6857140
العقود والشبكات على السلسلة
يتم تنفيذ Secure Lock كعقد خزانة قفل على السلسلة (يُشار إليه في هذا المستند باسم locker contract).
- اسم العقد: عادةً
SecureLock(راجع مستودعcheese-lockلمعرفة الاسم الدقيق و ABI). - الشبكات: المكوّن محايد للسلسلة (chain agnostic)، لكن النشر الإنتاجي الحالي يركز على سلاسل EVM.
من وثائق المنتج: “BSC is live today, but the component is chain agnostic. Use uppercase network tickers.” - العناوين الرسمية: يتم الاحتفاظ بعناوين locker contract لكل سلسلة مدعومة في مستودع
cheese-lockوتكوين النشر الداخلي لـ CheesePad.
عند التكامل:
- احصل دائمًا على عنوان الخزانة الرسمي من:
- مستودع GitHub
cheese-lock، أو - وثائق/إعلانات CheesePad الرسمية، أو
- تأكيد مباشر من فريق CheesePad الأساسي.
- مستودع GitHub
- لا تعتمد على نسخ عناوين العقود من أطراف ثالثة.
- تحقق من أن:
- bytecode يطابق المصدر المنشور على مستكشف الكتل المناسب.
- العنوان مملوك ومعلن من فريق CheesePad الرسمي.
المستكشفات التابعة لجهات خارجية (مثل أدوات DEX) عادةً:
- تراقب أحداث locker contract لاكتشاف إنشاء الأقفال ونشاط فك القفل.
- تربط كل سجل على السلسلة بـ نموذج بيانات Secure Lock الموضح أدناه.
- تعرض بطاقات القفل والتفاصيل باستخدام حقول status و schedule و amount.
أنواع الأقفال التي ندعمها
على مستوى Solidity، يعرض عقد CheeseLock بنية Lock واحدة.
تمثل “أنواع الأقفال” المختلفة عبر تركيبات من الحقول ودوال الإنشاء وليس عبر enum على السلسلة.
- Asset type (token vs LP):
- يتم تحديده بواسطة العلم
isLpTokenالممرر إلىlockأوvestingLockأوmultipleVestingLock. - داخليًا، تُسجّل أقفال LP عبر
_lockLpTokenوتُسجّل الأقفال غير LP عبر_lockNormalToken. - بالنسبة لأقفال LP يتم ضبط
cumulativeLockInfo[token].factoryعلى factory الخاص بـ LP؛ أما التوكنات العادية فهيaddress(0).
- يتم تحديده بواسطة العلم
- Schedule type (normal vs vesting):
- Normal lock: يُنشأ عبر
lock(...)ويُخزن كـLockحيثtgeBps == 0وcycle == 0وcycleBps == 0. يتم فك القفل مرة واحدة عندtgeDate. - Vesting lock: يُنشأ عبر
vestingLock(...)أوmultipleVestingLock(...)ويُخزن كـLockحيثtgeBps > 0وcycle > 0وcycleBps > 0وtgeBps + cycleBps <= 10_000. يتم فك القفل تدريجيًا مع الوقت.
- Normal lock: يُنشأ عبر
فوق ذلك، يمكن لطبقة المنتج/الواجهة وضع تسميات للأقفال مثل “LP Lock” و “Team allocation” وغيرها، لكن هذه التسميات غير مشفّرة في عقد Solidity.
المفاهيم الأساسية
على مستوى عالٍ، يحتوي مثيل Secure Lock على:
- Lock identity: معرّف فريد عالميًا والمعاملة على السلسلة التي أنشأت القفل.
- Scope: أي توكن أو زوج LP تم قفله وعلى أي شبكة.
- Amount: كمية التوكنات (أو توكنات LP) المقفلة، مع مرجع USD اختياري.
- Unlock schedule: متى وكيف تصبح الأصول المقفلة قابلة للمطالبة.
- Status: ما إذا كان القفل نشطًا أو مفكوكًا جزئيًا أو مكتملًا بالكامل أو مُلغى (إن وُجد).
تركّز وثائق المنتج على:
- الشفافية: صفحات مستكشف أقفال عامة كي يتمكن أي شخص من التحقق من أقفال المشروع.
- الاتساق: شرائح وبادجات واجهة توضح الجدول ونوع الأصل بوضوح. يتم تنفيذها في طبقة المنتج/المفهرس وليست جزءًا من واجهة Solidity.
نموذج البيانات على السلسلة
توثق هذه القسم هياكل Solidity الفعلية والسلوك القابل للملاحظة لعقد CheeseLock.
كل ما يلي مأخوذ مباشرةً من كود العقد في 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 islocks.length + ID_PADDINGat 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 == 0andcycle == 0andcycleBps == 0): the unlock timestamp. - For vesting locks: the TGE (Token Generation Event) timestamp when the first portion becomes claimable.
- For normal locks (
tgeBps:0for normal locks.- For vesting locks: basis points (out of
10_000) that unlock attgeDate.
cycle:0for normal locks.- For vesting locks: cycle duration in seconds.
cycleBps:0for normal locks.- For vesting locks: basis points of
amountthat unlock on each cycle after TGE.
unlockedAmount: Total amount already withdrawn by the owner.description: Free-form text; editable viaeditLockDescription.
Normal vs vesting locks:
lock(...)creates a normal lock:tgeBps = 0,cycle = 0,cycleBps = 0.tgeDateis the single unlock timestamp.
vestingLock(...)andmultipleVestingLock(...)create vesting locks:- Require
tgeDate > now,cycle > 0,0 < tgeBps < 10_000,0 < cycleBps < 10_000, andtgeBps + cycleBps <= 10_000.
- Require
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).
- For LP locks: the LP’s factory (e.g. UniswapV2‑compatible factory), inferred via
amount: Sum ofamountminusunlockedAmountover 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
LockAddedto detect new locks (both normal and vesting). - Listen to
LockRemovedandLockVestedto track unlocks and remaining balances. - Use
LockUpdatedto pick up changes toamountortgeDate. - Use
LockDescriptionChangedto refresh any cached human‑readable description. - Use
LockOwnerChangedto 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
amounttomsg.sender, setsunlockedAmount = amount, updates cumulative totals, and emitsLockRemoved.
- Requires
- 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 bothLockRemovedandLockVested. - Always emits
LockVestedwithamountunlocked in this call, remaining amount, and timestamp.
- Uses
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:tgeBpsofamountbecomes available. - After each multiple of
cycleseconds sincetgeDate: an additionalcycleBpsofamountis 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.
- Creates a normal lock that unlocks fully at
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
vestingLockfor multiple owners.
- Batch version of
- Unlocking
unlock(lockId)- If lock is vesting (
tgeBps > 0), calls_vestingUnlock. - Otherwise, calls
_normalUnlock.
- If lock is vesting (
withdrawableTokens(lockId) view returns (uint256)- Convenience view returning
_withdrawableTokens(getLockById(lockId)).
- Convenience view returning
- Editing metadata
editLock(lockId, newAmount, newUnlockDate)- Owner‑only. Can increase amount (not decrease) and/or push
tgeDateforward.
- Owner‑only. Can increase amount (not decrease) and/or push
editLockDescription(lockId, description)- Owner‑only. Updates
description.
- Owner‑only. Updates
- Ownership
transferLockOwnership(lockId, newOwner)renounceLockOwnership(lockId)(sets owner toaddress(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.
مثال لنموذج بيانات للمفهرسين (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)
}
أمثلة الاستخدام
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
}
مراجعة طلب قفل (للعمليات / المراجعين)
The Secure Lock product documentation suggests the following checklist for manual review:
- Confirm the token address matches the Launchpad listing (if applicable).
- Ensure liquidity is routed to the locker before the public pool goes live.
- Store transaction hashes in the CMS so the leaderboard or analytics teams can surface them later.
- 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.
قائمة تحقق للشفافية العامة والعرض (للمستكشفات / التحليلات)
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.
- Display lock cards on routes like
- Links and sharing
- Include a
View on explorerlink (e.g. BscScan) for the lock creation transaction and token contract. - Include a
Sharebutton so teams can promote their proof of lock on social platforms.
- Include a
- 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.
- Use clear chips like
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.