פיתוח מערכת יצירה (Crafting) ל-NFT על חוזים חכמים

האם אוסף ה-NFT שלך גדל, אבל השחקנים מאבדים עניין והכלכלה קופאת על שמריה? אנחנו בונים מערכות יצירה (Crafting) ל-NFT על חוזים חכמים שהופכות טוקנים שנצברו לפריטים בעלי ערך ומניעות מחדש לולאת משחק פעילה. הצוות שלנו מספק פרויקטים סוהריים — מבדיקת קוד ועיצוב מכניקה ועד הטמעה ותמיכה שוטפת — ומבטיח פתרון אמין וסקלבילי.

שירותי פיתוח בלוקצ'יין

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1481
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1335
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1034
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1293
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    738
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1031

יצרתם אוסף 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 ומערכות שדרוג. אנו מבטיחים שקיפות קוד, עמידה בלוחות זמנים ותמיכה לאחר השחרור.

תהליך

אנו עובדים בשלבים:

  1. אנליטיקה — סקירת הכלכלה והטוקנומיקה שלכם, גיבוש מפרט.
  2. עיצוב — ארכיטקטורת חוזה חכם, בחירת תקן, הערכת גז.
  3. פיתוח — כתיבת חוזים, בדיקות יחידה (Foundry), אינטגרציית VRF.
  4. ביקורת — סקירת קוד פנימית וחיצונית, תיקון פרצות.
  5. פריסה ובדיקות — testnet, סימולציית עומסים, התאמות.

לוחות זמנים ועלות

מערכת יצירה בסיסית (מתכונים + מיזוג + פלט דטרמיניסטי) אורכת 3 עד 4 שבועות. עם יצירה אקראית VRF ומערכת שדרוג — 5 עד 7 שבועות. העלות מחושבת באופן אישי לפי מספר המתכונים וההתאמות הנדרשות. צרו קשר — נעריך את הפרויקט שלכם ונציע פתרון אופטימלי.

דפוסי ממשק יצירה: חריצי גרירה ושחרור לחומרים, תצוגה מקדימה של תוצאה לפני יצירה, הסתברויות למתכונים אקראיים, אנימציית יצירה (פס התקדמות או אפקט חלקיקים).