יצרתם אוסף NFT; שחקנים מטביעים וסוחרים באופן פעיל — אבל מה הלאה? ללא מנגנון שצורך טוקנים קיימים ומעודד אינטראקציה, הכלכלה מתקפאת במהירות. נתקלנו בזה בפרויקט P2E עם אלפי פריטים שלא נדרשו: שחקנים פשוט אגרו אותם ללא מטרה. הפתרון היה מערכת יצירה (crafting) — היכולת לשלב מספר NFTs או משאבים כדי ליצור פריט חדש בעל ערך. זה יוצר מחזור כלכלי: מנגנון "כיור" (sink) מנקז נזילות, והתגמול שומר על שחקנים. בפרויקט אחד, השגנו הפחתה של 70% במחזור של NFTs ברמה נמוכה באמצעות יצירה מעוצבת היטב, ועלות העסקה הממוצעת ב-Polygon הייתה רק $0.02 — בערך פי 175 זול יותר מאשר ברשת הראשית של Ethereum ($3.50). חיסכון בגז עם יישום נכון יכול להגיע ל-40%.
איך מערכת יצירת NFT עובדת?
יצירה היא לא רק קריאה ל-safeTransferFrom. מתחת למכסה המנוע, יש דפוסים ברורים לשריפה (burning), הטבעה (minting) ואימות מתכונים. בואו נסתכל על הסוגים העיקריים:
| סוג | תיאור | דוגמה | אקראיות | הערות |
|---|---|---|---|---|
| מיזוג (Fusion) | N טוקנים מאותו סוג → טוקן אחד ברמה גבוהה יותר | 3 חרבות נפוצות → חרב נדירה אחת | לא | מפשט את המלאי, יוצר ביקוש ל-NFTs ברמה נמוכה |
| מתכון (Recipe) | שילובי חומרים ספציפיים → תוצאה ספציפית | 1 עפרת ברזל + 2 פחם + 1 תמצית אש → מטיל פלדה | לא | דטרמיניסטי, מתאים ליצירת פריטים מוגבלים |
| אקראי (Random) | חומרים + VRF → תוצאה מטווח | מתכלים → פריט אקראי ממאגר (נפוץ עד אגדי) | כן (Chainlink VRF) | סיכון/תגמול; מגביר ביקוש לחומרים |
| שדרוג (Upgrade) | NFT קיים + חומרים → אותו NFT עם מאפיינים משופרים | חרב רמה 1 + 10 תמציות → חרב רמה 2 | חלקי (הצלחה/כישלון) | בסגנון MMO קוריאני: אפשר לאבד את הפריט |
השוואת רשתות ליעילות יצירה
| רשת | עלות גז ממוצעת ליצירה (דולר) | זמן בלוק | L2 rollup | המלצה |
|---|---|---|---|---|
| Ethereum mainnet | $3.50 | 12 שניות | לא | רק לפריטים בעלי ערך גבוה |
| Polygon (zkEVM) | $0.02 | 2 שניות | כן | האיזון הטוב ביותר בין מחיר למהירות |
| Arbitrum One | $0.15 | 0.25 שניות | כן | למחזורי שדרוג מהירים |
| BNB Chain | $0.05 | 3 שניות | לא | חיסכון בעלויות ליצירות תכופות |
דוגמת יישום ב-Solidity
ליצירות אקראיות אנו משתמשים ב-Chainlink VRF — כל פעולה מאומתת על ידי אקראיות הוגנת. להלן קטע חוזה התומך גם במתכונים דטרמיניסטיים וגם ביצירה אקראית. הקוד המלא זמין במאגר שלנו.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract NFTCraftingSystem is AccessControl, VRFConsumerBaseV2Plus {
bytes32 public constant RECIPE_MANAGER = keccak256("RECIPE_MANAGER");
struct CraftingRecipe {
uint256 recipeId;
string name;
// Входящие материалы
address[] inputContracts; // адреса NFT контрактов материалов
uint256[] inputTokenIds; // tokenId (0 = любой из коллекции)
uint256[] inputAmounts; // количество (для ERC-1155)
// Входящие ERC-20 токены
address[] tokenInputs;
uint256[] tokenAmounts;
// Выход
address outputContract;
uint256 outputTokenId; // 0 = random из диапазона
uint256 minOutputId; // для random: минимальный tokenId
uint256 maxOutputId; // для random: максимальный tokenId
bool burnInputs; // сжигать или только потреблять
bool requiresVRF; // нужен ли random
bool isActive;
uint256 cooldown; // секунды между крафтингами одним адресом
}
mapping(uint256 => CraftingRecipe) public recipes;
mapping(address => mapping(uint256 => uint256)) public lastCraftTime; // player → recipeId → timestamp
mapping(uint256 => PendingCraft) public pendingCrafts; // vrfRequestId → craft
struct PendingCraft {
address crafter;
uint256 recipeId;
bool fulfilled;
}
function craft(uint256 recipeId, uint256[][] calldata inputTokenIds) external returns (uint256 requestId) {
CraftingRecipe storage recipe = recipes[recipeId];
require(recipe.isActive, "Recipe not active");
// Cooldown check
require(
block.timestamp >= lastCraftTime[msg.sender][recipeId] + recipe.cooldown,
"Crafting cooldown active"
);
lastCraftTime[msg.sender][recipeId] = block.timestamp;
// Валидируем и забираем материалы
_consumeInputMaterials(recipe, inputTokenIds);
_consumeInputTokens(recipe);
if (recipe.requiresVRF) {
// Для рандомного крафтинга — запрашиваем VRF
requestId = _requestRandomWords(1);
pendingCrafts[requestId] = PendingCraft({
crafter: msg.sender,
recipeId: recipeId,
fulfilled: false
});
emit CraftingInitiated(msg.sender, recipeId, requestId);
} else {
// Детерминированный крафтинг — минтим сразу
_mintCraftingResult(msg.sender, recipe, 0);
}
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
PendingCraft storage pending = pendingCrafts[requestId];
require(!pending.fulfilled, "Already fulfilled");
pending.fulfilled = true;
CraftingRecipe storage recipe = recipes[pending.recipeId];
_mintCraftingResult(pending.crafter, recipe, randomWords[0]);
}
function _mintCraftingResult(
address crafter,
CraftingRecipe storage recipe,
uint256 random
) internal {
uint256 outputTokenId;
if (recipe.outputTokenId != 0) {
// Детерминированный output
outputTokenId = recipe.outputTokenId;
} else {
// Random output в диапазоне [minOutputId, maxOutputId]
outputTokenId = recipe.minOutputId + (random % (recipe.maxOutputId - recipe.minOutputId + 1));
}
// Минтим результат
IGameItems(recipe.outputContract).mintCraftingResult(crafter, outputTokenId, 1);
emit CraftingCompleted(crafter, recipe.recipeId, outputTokenId);
}
function _consumeInputMaterials(
CraftingRecipe storage recipe,
uint256[][] calldata inputTokenIds
) internal {
for (uint i = 0; i < recipe.inputContracts.length; i++) {
IERC1155 nft = IERC1155(recipe.inputContracts[i]);
if (recipe.burnInputs) {
// Burn материалы
IERC1155Burnable(recipe.inputContracts[i]).burn(
msg.sender,
inputTokenIds[i][0],
recipe.inputAmounts[i]
);
} else {
// Перевести в контракт (без сжигания)
nft.safeTransferFrom(
msg.sender,
address(this),
inputTokenIds[i][0],
recipe.inputAmounts[i],
""
);
}
}
}
} מערכת שדרוג (קידום מאפיינים)
למשחקים שבהם פריטים צריכים שיפור, אנו מיישמים חוזה נפרד התומך ברמות, חומרים וסיכויי הצלחה. מכניקת הרס-בכישלון (בסגנון MMO קוריאני) מגדילה משמעותית את הערך של פריטים ברמה גבוהה.
contract NFTUpgradeSystem {
struct UpgradePath {
uint256 itemTypeId;
uint256 currentLevel;
uint256 maxLevel;
uint256[] materialCosts; // материалы для каждого уровня
uint256[] tokenCosts;
uint256 successRate; // в basis points, 10000 = 100%
bool destroyOnFail; // сжигать при неудаче?
}
// Upgrade с риском уничтожения (Korean-MMO стиль)
function upgradeItem(
uint256 tokenId,
uint256 itemTypeId,
uint256 targetLevel
) external returns (bool success) {
UpgradePath storage path = upgradePaths[itemTypeId][targetLevel];
// Забираем материалы
_burnUpgradeMaterials(path);
// Определяем успех (off-chain random или VRF)
// Для простоты — pseudo-random через block hash
uint256 rand = uint256(keccak256(abi.encodePacked(
blockhash(block.number - 1),
msg.sender,
tokenId,
block.timestamp
))) % 10000;
success = rand < path.successRate;
if (success) {
gameItems.setItemLevel(tokenId, targetLevel);
emit UpgradeSuccess(msg.sender, tokenId, targetLevel);
} else if (path.destroyOnFail) {
gameItems.burn(msg.sender, itemTypeId, 1);
emit UpgradeFailed(msg.sender, tokenId, targetLevel, true);
} else {
// Просто неудача без потери предмета
emit UpgradeFailed(msg.sender, tokenId, targetLevel, false);
}
}
}לשדרוג עם סיכון הרס, VRF הוא חובה — השחקן חייב להיות בטוח שהקזינו לא יכול לתמרן את הסיכויים.
למה אימות חומרים נכון חשוב?
שגיאה בלוגיקת // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/AccessControl.sol"; contract NFTCraftingSystem is AccessControl, VRFConsumerBaseV2Plus { bytes32 public constant RECIPE_MANAGER = keccak256("RECIPE_MANAGER"); struct CraftingRecipe { uint256 recipeId; string name; // Входящие материалы address[] inputContracts; // адреса NFT контрактов материалов uint256[] inputTokenIds; // tokenId (0 = любой из коллекции) uint256[] inputAmounts; // количество (для ERC-1155) // Входящие ERC-20 токены address[] tokenInputs; uint256[] tokenAmounts; // Выход address outputContract; uint256 outputTokenId; // 0 = random из диапазона uint256 minOutputId; // для random: минимальный tokenId uint256 maxOutputId; // для random: максимальный tokenId bool burnInputs; // сжигать или только потреблять bool requiresVRF; // нужен ли random bool isActive; uint256 cooldown; // секунды между крафтингами одним адресом } mapping(uint256 => CraftingRecipe) public recipes; mapping(address => mapping(uint256 => uint256)) public lastCraftTime; // player → recipeId → timestamp mapping(uint256 => PendingCraft) public pendingCrafts; // vrfRequestId → craft struct PendingCraft { address crafter; uint256 recipeId; bool fulfilled; } function craft(uint256 recipeId, uint256[][] calldata inputTokenIds) external returns (uint256 requestId) { CraftingRecipe storage recipe = recipes[recipeId]; require(recipe.isActive, "Recipe not active"); // Cooldown check require( block.timestamp >= lastCraftTime[msg.sender][recipeId] + recipe.cooldown, "Crafting cooldown active" ); lastCraftTime[msg.sender][recipeId] = block.timestamp; // Валидируем и забираем материалы _consumeInputMaterials(recipe, inputTokenIds); _consumeInputTokens(recipe); if (recipe.requiresVRF) { // Для рандомного крафтинга — запрашиваем VRF requestId = _requestRandomWords(1); pendingCrafts[requestId] = PendingCraft({ crafter: msg.sender, recipeId: recipeId, fulfilled: false, }); emit CraftingInitiated(msg.sender, recipeId, requestId); } else { // Детерминированный крафтинг — минтим сразу _mintCraftingResult(msg.sender, recipe, 0); } } function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override { PendingCraft storage pending = pendingCrafts[requestId]; require(!pending.fulfilled, "Already fulfilled"); pending.fulfilled = true; CraftingRecipe storage recipe = recipes[pending.recipeId]; _mintCraftingResult(pending.crafter, recipe, randomWords[0]); } function _mintCraftingResult( address crafter, CraftingRecipe storage recipe, uint256 random ) internal { uint256 outputTokenId; if (recipe.outputTokenId != 0) { // Детерминированный output outputTokenId = recipe.outputTokenId; } else { // Random output в диапазоне [minOutputId, maxOutputId] outputTokenId = recipe.minOutputId + (random % (recipe.maxOutputId - recipe.minOutputId + 1)); } // Минтим результат IGameItems(recipe.outputContract).mintCraftingResult(crafter, outputTokenId, 1); emit CraftingCompleted(crafter, recipe.recipeId, outputTokenId); } function _consumeInputMaterials( CraftingRecipe storage recipe, uint256[][] calldata inputTokenIds ) internal { for (uint i = 0; i < recipe.inputContracts.length; i++) { IERC1155 nft = IERC1155(recipe.inputContracts[i]); if (recipe.burnInputs) { // Burn материалы IERC1155Burnable(recipe.inputContracts[i]).burn( msg.sender, inputTokenIds[i][0], recipe.inputAmounts[i] ); } else { // Перевести в контракт (без сжигания) nft.safeTransferFrom( msg.sender, address(this), inputTokenIds[i][0], recipe.inputAmounts[i], "" ); } } } } היא אחת הסיבות הנפוצות ביותר לפריצות יצירה. צריך לבדוק:
- התאמת כתובות חוזה ו-tokenIds מותרים.
- יתרת שחקן לפני העברה, במיוחד במצב שריפה (העברה שורפת מיד, לא מחזיקה זמנית).
- היעדר reentrancy — השתמשו ב-OpenZeppelin ReentrancyGuard.
- טיפול נכון ב-ERC-1155 batchTransfer למתכונים מרובי רכיבים.
לפי תיעוד OpenZeppelin ReentrancyGuard, הוא מונע reentrancy, שהוא קריטי לפעולות הכוללות שריפה והטבעה.
מה כלול בפיתוח?
אנו מספקים את המחזור המלא:
- ניתוח כלכלת משחק ועיצוב מתכונים.
- חוזים חכמים ליצירה (Solidity 0.8.x, ארכיטקטורה מודולרית).
- אינטגרציה עם Chainlink VRF לאקראיות.
- פיתוח ממשק משתמש (חריצי גרירה ושחרור, תצוגה מקדימה של תוצאה, אנימציה).
- פריסה לרשת הנבחרת (Ethereum, Polygon, Arbitrum, BNB Chain).
- תיעוד מלא (ארכיטקטורה, ממשקים, סקריפטים לפריסה).
- ביקורת אבטחה (Slither, Mythril, Echidna).
- אחריות קוד — 6 חודשים של תיקוני באגים.
הזמינו פיתוח של מערכת יצירת NFT וקבלו ייעוץ עם אב טיפוס ברשת testnet. הניסיון שלנו: מעל 20 פרויקטים מיושמים, כולל אינטגרציה עם VRF ומערכות שדרוג. אנו מבטיחים שקיפות קוד, עמידה בלוחות זמנים ותמיכה לאחר השחרור.
תהליך
אנו עובדים בשלבים:
- אנליטיקה — סקירת הכלכלה והטוקנומיקה שלכם, גיבוש מפרט.
- עיצוב — ארכיטקטורת חוזה חכם, בחירת תקן, הערכת גז.
- פיתוח — כתיבת חוזים, בדיקות יחידה (Foundry), אינטגרציית VRF.
- ביקורת — סקירת קוד פנימית וחיצונית, תיקון פרצות.
- פריסה ובדיקות — testnet, סימולציית עומסים, התאמות.
לוחות זמנים ועלות
מערכת יצירה בסיסית (מתכונים + מיזוג + פלט דטרמיניסטי) אורכת 3 עד 4 שבועות. עם יצירה אקראית VRF ומערכת שדרוג — 5 עד 7 שבועות. העלות מחושבת באופן אישי לפי מספר המתכונים וההתאמות הנדרשות. צרו קשר — נעריך את הפרויקט שלכם ונציע פתרון אופטימלי.
דפוסי ממשק יצירה: חריצי גרירה ושחרור לחומרים, תצוגה מקדימה של תוצאה לפני יצירה, הסתברויות למתכונים אקראיים, אנימציית יצירה (פס התקדמות או אפקט חלקיקים).







