בניסיון שלנו, נתקלנו בעשרות אינטגרציות של חברות NFT וראינו כיצד ארכיטקטורת חוזה שגויה מובילה לדליפות הרשאות. הטעות הנפוצה ביותר: מפתחים מיישמים בדיקה כמו ownerOf(tokenId) == msg.sender ורואים בכך פתרון. אבל NFTs יכולים להיות מושאלים, flash loaned (לבלוק אחד), או להיות רשומים במרקטפלייס תוך שמירת גישה דרך delegation. מערכת חברות תקינה דורשת הבנה של וקטורים אלה ובחירה מפורשת של מודל אמון. הניסיון שלנו — 5 שנים ב-DeFi ו-NFT, מעל 20 פרויקטים מצליחים — מבטיח פתרון אמין.
ארכיטקטורת חוזה
מודל בסיסי: בעלות על טוקן
למקרי שימוש פשוטים (גישה לתוכן, אימות Discord), ERC-721 עם בדיקת balanceOf(user) > 0 מספיק. balanceOf זול יותר מ-ownerOf עבור מספר טוקנים ועמיד יותר מול מקרי קצה. עם זאת, זה לא מגן מפני רישום: הבעלים יכול לרשום את ה-NFT ב-OpenSea, לגשת לתוכן מוגבל, ואז לבטל את הרישום.
חברות מדורגת באמצעות ERC-1155
עבור רמות גישה מרובות (ברונזה/כסף/זהב, או חודש/שנה/לכל החיים), ERC-1155 טוב יותר מטבעו מ-ERC-721. כל tokenId מייצג רמה:
uint256 public constant TIER_BRONZE = 1;
uint256 public constant TIER_SILVER = 2;
uint256 public constant TIER_GOLD = 3;
function getMemberTier(address user) external view returns (uint256) {
if (balanceOf(user, TIER_GOLD) > 0) return TIER_GOLD;
if (balanceOf(user, TIER_SILVER) > 0) return TIER_SILVER;
if (balanceOf(user, TIER_BRONZE) > 0) return TIER_BRONZE;
return 0; // не участник
}רמות עם גישה מצטברת: זהב כולל את כל מה שבכסף וברונזה. אנו בודקים מלמעלה למטה.
למה להשתמש ב-Soulbound Tokens לחברות?
Soulbound tokens (EIP-5192) מבטלים את בעיית ההעברה: הם מחוברים לצמיתות לכתובת הבעלים. אם המטרה היא לחבר גישה לאדם ספציפי ולא לארנק, השתמשו ב-EIP-5192 או פשוט דרסו פונקציות העברה:
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override {
require(from == address(0) || to == address(0), "Soulbound: non-transferable");
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}uint256 public constant TIER_BRONZE = 1; uint256 public constant TIER_SILVER = 2; uint256 public constant TIER_GOLD = 3; function getMemberTier(address user) external view returns (uint256) { if (balanceOf(user, TIER_GOLD) > 0) return TIER_GOLD; if (balanceOf(user, TIER_SILVER) > 0) return TIER_SILVER; if (balanceOf(user, TIER_BRONZE) > 0) return TIER_BRONZE; return 0; // не участник } — mint, function _beforeTokenTransfer( address from, address to, uint256 tokenId, uint256 batchSize ) internal override { require(from == address(0) || to == address(0), "Soulbound: non-transferable"); super._beforeTokenTransfer(from, to, tokenId, batchSize); } — burn. כל השאר אסור. בעיה: אובדן מפתחות פירושו אובדן חברות. פתרון: ספקו מנגנון שחזור באמצעות multisig או שחזור חברתי (ERC-4337 account abstraction).
איך ליישם מנויים מבוססי זמן ב-ERC-721?
חברות שפגה דורשת אחסון תאריכים. שתי גישות:
| גישה | חותמת זמן on-chain | מבוסס חתימה off-chain |
|---|---|---|
| אחסון | מיפוי tokenId → expiresAt | JWT חתום עם תפוגה (בשרת backend) |
| גז עבור בדיקה | כתיבת אחסון | ללא כתיבות, זול יותר |
| אמון | מלא (הכל on-chain) | דורש אמון בשירות החתימה |
| מתאים ל | מערכות on-chain מלאות | אינטגרציות Web2-היברידיות ו-API |
למערכות on-chain מלאות, השתמשו בגישה הראשונה. ERC-5643 הוא תקן טיוטה למנויי NFT עם from == address(0).
השוואת מודלי חברות
| תכונה | ERC-721 | ERC-1155 | Soulbound (EIP-5192) |
|---|---|---|---|
| רמות | סוג יחיד | רמות מרובות | סוג יחיד (אם אחד) |
| העברה | כן | כן | לא |
| גז עבור בדיקה | to == address(0) ~30k |
renewSubscription(uint256 tokenId, uint64 duration) ~40k |
balanceOf ~30k |
| מורכבות | נמוכה | בינונית | בינונית |
| יישום | גישה פשוטה | חברות מדורגת | מנויים אישיים |
אינטגרציה עם מערכות Off-Chain
אימות באמצעות EIP-1271
לאימות חברות בשרת backend ללא עסקאות: המשתמש חותם על הודעה (EIP-191 או EIP-712), השרת מאמת באמצעות balanceOfBatch ל-balanceOf עבור ארנקים חכמים או באמצעות eth_call עבור EOAs. זו בדיקת חברות off-chain סטנדרטית.
Delegation באמצעות delegate.cash
delegate.cash (תקן דה-פקטו) מאפשר לבעלי NFT להאציל מארנק קר לארנק חם. עבור מערכות חברות, זה חשוב: מחזיקים שומרים NFTs יקרים בארנקים קרים ומתקשרים דרך ארנקים חמים. אינטגרציה:
IDelegationRegistry constant DELEGATION_REGISTRY = IDelegationRegistry(0x00000000000076A84feF008CDAbe6409d2FE638B);
function isMember(address user) public view returns (bool) {
if (balanceOf(user) > 0) return true;
// Проверяем делегирование
address[] memory delegators = DELEGATION_REGISTRY.getDelegationsByDelegate(user);
for (uint i = 0; i < delegators.length; i++) {
if (balanceOf(delegators[i]) > 0) return true;
}
return false;
}זהו צורך אמיתי: Moonbirds, Doodles, וקולקציות מרכזיות אחרות שילבו delegate.cash בדיוק לשם כך.
מנגנון Mint ותמחור
Allowlist באמצעות Merkle Tree הוא הסטנדרט למכירה מוקדמת. חיסכון בגז מגיע ל-70% בהשוואה לאחסון הרשימה on-chain:
bytes32 public merkleRoot;
function allowlistMint(bytes32[] calldata proof) external payable {
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
require(msg.value >= PRICE, "Insufficient payment");
_safeMint(msg.sender, _nextTokenId());
}ההוכחה נוצרת off-chain (merkletreejs), השורש מועלה לחוזה. רשימה של 10,000 כתובות מביאה להוכחה של ~14 hashes, calldata ~450 בתים.
דוגמה לחישוב הוכחת Merkle עבור 10,000 כתובות
עץ בעומק 14 כולל 16,384 עלים. עבור הוכחה של 14 hashes (32 בתים כל אחד), calldata הוא 14*32 + 4 (offset) ≈ 452 בתים. במחיר גז של 100 gwei, החיסכון בהשוואה לאחסון הרשימה on-chain הוא כ-0.005 ETH.
מה כלול בעבודה
- ביקורת ארכיטקטורה קיימת ומפרט דרישות
- פיתוח חוזים חכמים (Solidity 0.8.x, OpenZeppelin)
- כתיבת בדיקות יחידה (Foundry/Hardhat) עם כיסוי מקרי קצה
- אינטגרציה עם backend (EIP-1271, delegate.cash) ו-frontend (wagmi, RainbowKit)
- תיעוד חוזה (NatSpec) והוראות פריסה
- פריסה ל-mainnet/testnet, הגדרת אימות ב-Etherscan
- תמיכה לאחר השקה (אופציונלי)
הערכות זמנים
חברות ERC-721 עם רמות ו-Merkle allowlist — מיומיים. הוספת מנויים מבוססי זמן (בסגנון ERC-5643) בתוספת אימות backend — עוד 1-2 ימים. מערכת מלאה עם delegation, שחזור soulbound, ו-frontend — 4-5 ימים. צרו קשר להערכה מדויקת של הפרויקט שלכם. הזמינו פיתוח של מערכת החברות שלכם וקבלו ייעוץ מהמהנדס שלנו.







