Skip to main content
Let players combine existing NFTs to create new ones—a core mechanic for progression and engagement in games.

Why Use Crafting?

Crafting drives collection and trading behaviour. Players actively hunt for ingredients, creating demand across your economy.
Burning inputs removes items from circulation, maintaining scarcity and value in your game economy.
Multi-step recipes create meaningful goals. Players feel invested when they craft rare items.

How It Works

1

Player Selects Items

Player chooses NFTs to combine from their inventory
2

Backend Validates

Your server verifies ownership and recipe validity
3

Burn Inputs

Transfer input NFTs to burn address
4

Mint Output

Create new NFT for player via Minting API

Recipe System

Define recipes that map inputs to outputs:
const recipes = [
  {
    id: 'iron_sword',
    inputs: [
      { contract: MATERIALS, trait: { type: 'Iron Ore' }, count: 2 },
      { contract: MATERIALS, trait: { type: 'Wood' }, count: 1 },
    ],
    output: {
      name: 'Iron Sword',
      attributes: [{ trait_type: 'Damage', value: 10 }],
    },
  },
  {
    id: 'steel_sword',
    inputs: [
      { contract: WEAPONS, trait: { type: 'Iron Sword' }, count: 1 },
      { contract: MATERIALS, trait: { type: 'Steel Ingot' }, count: 2 },
    ],
    output: {
      name: 'Steel Sword',
      attributes: [{ trait_type: 'Damage', value: 25 }],
    },
  },
];

Implementation

const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD';

app.post('/craft', async (req, res) => {
  const { recipeId, inputTokenIds, playerAddress } = req.body;
  const recipe = recipes.find(r => r.id === recipeId);
  
  if (!recipe) {
    return res.status(400).json({ error: 'Invalid recipe' });
  }
  
  // Verify ownership of all inputs
  for (const tokenId of inputTokenIds) {
    const owner = await nftContract.ownerOf(tokenId);
    if (owner.toLowerCase() !== playerAddress.toLowerCase()) {
      return res.status(403).json({ error: 'Not owner of input' });
    }
  }
  
  // Verify inputs match recipe requirements
  // ... validation logic
  
  // Burn inputs
  for (const tokenId of inputTokenIds) {
    await nftContract.transferFrom(playerAddress, BURN_ADDRESS, tokenId);
  }
  
  // Mint output via Minting API
  const outputId = generateTokenId();
  await mintNFT(playerAddress, outputId, recipe.output);
  
  res.json({ success: true, outputId });
});

Upgrades

Enhance existing items by burning materials:
async function upgradeItem(tokenId: string, materialsUsed: string[]) {
  const nft = await getNFT(tokenId);
  const level = nft.attributes.find(a => a.trait_type === 'Level')?.value || 1;
  
  // Burn material NFTs
  for (const materialId of materialsUsed) {
    await burnNFT(materialId);
  }
  
  // Update metadata via Minting API refresh
  await refreshMetadata(tokenId, {
    ...nft.metadata,
    attributes: nft.attributes.map(a => 
      a.trait_type === 'Level' ? { ...a, value: level + 1 } : a
    ),
  });
}

Design Tips

PatternDescription
DeterministicSame inputs always produce same output
ProbabilisticRandom chance for rare outcomes
Time-gatedCrafting takes time to complete
ProgressiveMulti-step recipes for advanced items
Keep crafting server-side to prevent exploitation. Only mint outputs after verifying ownership and burning inputs.