You created an NFT collection; players are actively minting and trading — but what's next? Without a mechanism that consumes existing tokens and motivates interaction, the economy quickly stagnates. We encountered this in a P2E project with thousands of unclaimed items: players simply hoarded them without purpose. The solution was a crafting system — the ability to combine multiple NFTs or resources to create a new valuable item. This creates an economic cycle: a sink mechanism drains liquidity, and the reward retains players. In one project, we achieved a 70% reduction in turnover of low-level NFTs through well-designed crafting, and the average transaction cost on Polygon was just $0.02 — about 175 times cheaper than on Ethereum mainnet ($3.50). Gas savings with proper implementation can reach 40%.
How does an NFT crafting system work?
Crafting is not just a call to safeTransferFrom. Under the hood, there are clear patterns for burning, minting, and recipe validation. Let's look at the main types:
| Type | Description | Example | Randomness | Notes |
|---|---|---|---|---|
| Fusion | N tokens of the same type → 1 higher-tier token | 3 Common swords → 1 Rare sword | No | Simplifies inventory, creates demand for low-level NFTs |
| Recipe | Specific material combinations → specific result | 1 Iron Ore + 2 Coal + 1 Fire Essence → Steel Ingot | No | Deterministic, suitable for limited-item crafting |
| Random | Materials + VRF → result from a range | Consumables → random item from pool (common to legendary) | Yes (Chainlink VRF) | Risk/reward; increases demand for materials |
| Upgrade | Existing NFT + materials → same NFT with improved attributes | Sword lvl 1 + 10 Essence → Sword lvl 2 | Partial (success/failure) | Korean-MMO style: can lose the item |
Network comparison for crafting efficiency
| Network | Average gas cost per craft (USD) | Block time | L2 rollup | Recommendation |
|---|---|---|---|---|
| Ethereum mainnet | $3.50 | 12 s | No | Only for high-value items |
| Polygon (zkEVM) | $0.02 | 2 s | Yes | Best balance of price and speed |
| Arbitrum One | $0.15 | 0.25 s | Yes | For fast upgrade cycles |
| BNB Chain | $0.05 | 3 s | No | Cost savings for frequent crafts |
Example implementation in Solidity
For random crafts we use Chainlink VRF — each operation is confirmed by fair randomness. Below is a contract snippet supporting both deterministic recipes and random crafting. Full code is available in our repository.
// 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;
// Input materials
address[] inputContracts; // addresses of material NFT contracts
uint256[] inputTokenIds; // tokenId (0 = any from collection)
uint256[] inputAmounts; // quantity (for ERC-1155)
// Input ERC-20 tokens
address[] tokenInputs;
uint256[] tokenAmounts;
// Output
address outputContract;
uint256 outputTokenId; // 0 = random from range
uint256 minOutputId; // for random: minimum tokenId
uint256 maxOutputId; // for random: maximum tokenId
bool burnInputs; // burn or only consume
bool requiresVRF; // whether random is needed
bool isActive;
uint256 cooldown; // seconds between crafting by the same address
}
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;
// Validate and collect materials
_consumeInputMaterials(recipe, inputTokenIds);
_consumeInputTokens(recipe);
if (recipe.requiresVRF) {
// For random crafting — request VRF
requestId = _requestRandomWords(1);
pendingCrafts[requestId] = PendingCraft({
crafter: msg.sender,
recipeId: recipeId,
fulfilled: false,
});
emit CraftingInitiated(msg.sender, recipeId, requestId);
} else {
// Deterministic crafting — mint immediately
_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) {
// Deterministic output
outputTokenId = recipe.outputTokenId;
} else {
// Random output in range [minOutputId, maxOutputId]
outputTokenId = recipe.minOutputId + (random % (recipe.maxOutputId - recipe.minOutputId + 1));
}
// Mint result
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 materials
IERC1155Burnable(recipe.inputContracts[i]).burn(
msg.sender,
inputTokenIds[i][0],
recipe.inputAmounts[i]
);
} else {
// Transfer to contract (without burning)
nft.safeTransferFrom(
msg.sender,
address(this),
inputTokenIds[i][0],
recipe.inputAmounts[i],
""
);
}
}
}
}
Upgrade system (attribute advancement)
For games where items need improvement, we implement a separate contract supporting levels, materials, and success chance. Destruction-on-failure mechanics (Korean-MMO style) dramatically increase the value of high-level items.
contract NFTUpgradeSystem {
struct UpgradePath {
uint256 itemTypeId;
uint256 currentLevel;
uint256 maxLevel;
uint256[] materialCosts; // materials for each level
uint256[] tokenCosts;
uint256 successRate; // in basis points, 10000 = 100%
bool destroyOnFail; // burn on failure?
}
// Upgrade with destruction risk (Korean-MMO style)
function upgradeItem(
uint256 tokenId,
uint256 itemTypeId,
uint256 targetLevel
) external returns (bool success) {
UpgradePath storage path = upgradePaths[itemTypeId][targetLevel];
// Collect materials
_burnUpgradeMaterials(path);
// Determine success (off-chain random or VRF)
// For simplicity — pseudo-random via 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 {
// Simple failure without item loss
emit UpgradeFailed(msg.sender, tokenId, targetLevel, false);
}
}
}
For upgrade with destruction risk, VRF is mandatory — the player must be confident that the casino cannot manipulate the odds.
Why is correct material validation important?
An error in the consumeInputMaterials logic is one of the most common causes of crafting hacks. You need to check:
- Matching contract addresses and allowed tokenIds.
- Player balance before transfer, especially in burn mode (transfer immediately burns, not temporarily holds).
- Absence of reentrancy — use OpenZeppelin ReentrancyGuard.
- Proper handling of ERC-1155 batchTransfer for multi-component recipes.
According to the OpenZeppelin ReentrancyGuard documentation, it prevents reentrancy, which is critical for operations involving burning and minting.
What's included in development?
We provide the full cycle:
- Game economy analysis and recipe design.
- Crafting smart contracts (Solidity 0.8.x, modular architecture).
- Integration with Chainlink VRF for randomness.
- Frontend UI development (drag-and-drop slots, result preview, animation).
- Deployment to the chosen network (Ethereum, Polygon, Arbitrum, BNB Chain).
- Full documentation (architecture, interfaces, deploy scripts).
- Security audit (Slither, Mythril, Echidna).
- Code warranty — 6 months of bug fixes.
Order the development of an NFT crafting system and receive a consultation with a prototype on testnet. Our experience: over 20 implemented projects, including integration with VRF and upgrade systems. We guarantee code transparency, timely milestone delivery, and post-release support.
Process
We work in stages:
- Analytics — review of your economy and tokenomics, specification formation.
- Design — smart contract architecture, standard selection, gas estimation.
- Development — writing contracts, unit tests (Foundry), VRF integration.
- Audit — internal and external code review, vulnerability fixes.
- Deployment and testing — testnet, load simulation, adjustments.
Timelines and cost
A basic crafting system (recipes + fusion + deterministic output) takes 3 to 4 weeks. With VRF random crafting and upgrade system — 5 to 7 weeks. Cost is calculated individually based on the number of recipes and required customization. Contact us — we will evaluate your project and propose the optimal solution.
Crafting UI patterns: drag-and-drop slots for materials, result preview before crafting, probabilities for random recipes, crafting animation (progress bar or particle effect).







