Skip to main content

What are NFTs?

NFTs (Non-Fungible Tokens) are unique digital items on the blockchain. Unlike tokens (which are identical and interchangeable), each NFT is one-of-a-kind.
Think of NFTs like trading cards. Each card is unique, even if there are multiple cards with the same artwork. Players truly own their NFTs and can trade or sell them.

NFT Use Cases in Games

Collectibles
  • Rare weapons or armor
  • Character skins
  • Trading cards
  • Achievement badges
Functional Items
  • Characters with unique stats
  • Land parcels in virtual worlds
  • Access passes (ticket NFTs)
  • Upgrade items
Player-Created Content
  • Custom levels as NFTs
  • User-designed items
  • Fan art and mods
Achievements
  • Permanent proof of accomplishments
  • “First to complete” NFTs
  • Leaderboard rewards

NFT Data Structure

G3Engine uses this structure for NFTs:
export interface NFTInfo {
  mint: string;              // Unique NFT address (token ID)
  name: string;              // NFT name (e.g., "Legendary Sword")
  imageUri: string;          // Image URL (required)
  collection?: string;       // Collection name (e.g., "Game Items")
  attributes?: Array<{       // NFT traits/properties
    trait_type: string;      // Attribute name (e.g., "Rarity")
    value: string;           // Attribute value (e.g., "Legendary")
  }>;
}

Example NFT

const swordNFT: NFTInfo = {
  mint: "8qJVc5evVxYg7kBx9K3DwNw5P1Fg3qZnN8K5TpK5pump",
  name: "Legendary Dragon Sword",
  imageUri: "https://cdn.game.com/items/sword-legendary.png",
  collection: "Adventure Quest Items",
  attributes: [
    { trait_type: "Rarity", value: "Legendary" },
    { trait_type: "Attack", value: "100" },
    { trait_type: "Durability", value: "999" },
    { trait_type: "Element", value: "Fire" },
    { trait_type: "Level Required", value: "50" }
  ]
}

Minting NFTs

Create new NFTs in your game:

Basic NFT Mint

import { executeMintNFT } from '@/lib/web3Runtime';

const result = await executeMintNFT(
  ctx,
  "Legendary Dragon Sword",              // NFT name
  "https://cdn.game.com/sword.png",      // Image URL
  {                                       // Attributes (optional)
    "Rarity": "Legendary",
    "Attack": "100",
    "Element": "Fire"
  }
);

console.log(`NFT minted: ${result.mintAddress}`);
console.log(`Transaction: ${result.txHash}`);

Mint Result

export interface MintNFTResult {
  mintAddress: string;   // The new NFT's unique address
  txHash: string;        // Blockchain transaction hash
}

Viewing Player NFTs

Display NFTs a player owns:
import { useWeb3Store } from '@/store/web3Store';

function NFTGallery() {
  const { nfts } = useWeb3Store();
  
  return (
    <div className="nft-gallery">
      <h2>Your NFTs</h2>
      <div className="nft-grid">
        {nfts.map(nft => (
          <div key={nft.mint} className="nft-card">
            <img src={nft.imageUri} alt={nft.name} />
            <h3>{nft.name}</h3>
            {nft.collection && <p>Collection: {nft.collection}</p>}
            
            {nft.attributes && (
              <div className="attributes">
                {nft.attributes.map(attr => (
                  <div key={attr.trait_type}>
                    <strong>{attr.trait_type}:</strong> {attr.value}
                  </div>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

NFT-Gated Content

Require players to own specific NFTs to unlock content:
// Check if player owns an NFT from a collection
function checkNFTOwnership(
  playerNFTs: NFTInfo[],
  collectionName: string
): boolean {
  return playerNFTs.some(nft => nft.collection === collectionName);
}

// Check if player owns a specific NFT
function ownsSpecificNFT(
  playerNFTs: NFTInfo[],
  nftMint: string
): boolean {
  return playerNFTs.some(nft => nft.mint === nftMint);
}

// Usage
const { nfts } = useWeb3Store();

if (checkNFTOwnership(nfts, "Premium Pass Collection")) {
  unlockPremiumArea();
} else {
  showMessage("You need a Premium Pass NFT to enter");
}

Attribute-Based Gating

Gate content based on NFT attributes:
// Require NFT with specific attribute value
function hasRequiredNFT(
  playerNFTs: NFTInfo[],
  requiredAttribute: string,
  requiredValue: string
): NFTInfo | undefined {
  return playerNFTs.find(nft => 
    nft.attributes?.some(attr => 
      attr.trait_type === requiredAttribute && 
      attr.value === requiredValue
    )
  );
}

// Example: Require "Legendary" rarity NFT
const legendaryNFT = hasRequiredNFT(nfts, "Rarity", "Legendary");

if (legendaryNFT) {
  console.log(`Access granted with ${legendaryNFT.name}`);
  unlockLegendaryQuest();
}

Rewarding NFTs

Give players NFTs for achievements:

Achievement NFTs

const achievementNFTs = {
  'first_win': {
    name: "First Victory Badge",
    imageUri: "https://cdn.game.com/badges/first-win.png",
    attributes: {
      "Type": "Achievement",
      "Date": new Date().toISOString()
    }
  },
  'speed_run': {
    name: "Speedrunner Trophy",
    imageUri: "https://cdn.game.com/badges/speedrun.png",
    attributes: {
      "Type": "Achievement",
      "Category": "Speed"
    }
  }
};

// When player unlocks achievement
async function rewardAchievement(achievementId: string) {
  const achievement = achievementNFTs[achievementId];
  if (!achievement) return;
  
  const result = await executeMintNFT(
    ctx,
    achievement.name,
    achievement.imageUri,
    achievement.attributes
  );
  
  addNotification(`You earned: ${achievement.name}!`);
}

Random Loot NFTs

const lootTable = [
  { name: "Common Sword", rarity: "Common", chance: 0.5 },
  { name: "Rare Axe", rarity: "Rare", chance: 0.3 },
  { name: "Epic Staff", rarity: "Epic", chance: 0.15 },
  { name: "Legendary Bow", rarity: "Legendary", chance: 0.05 }
];

function rollLoot() {
  const roll = Math.random();
  let cumulative = 0;
  
  for (const item of lootTable) {
    cumulative += item.chance;
    if (roll < cumulative) {
      return item;
    }
  }
}

// Mint random loot NFT
const loot = rollLoot();
await executeMintNFT(
  ctx,
  loot.name,
  `https://cdn.game.com/items/${loot.name.toLowerCase()}.png`,
  { "Rarity": loot.rarity }
);

NFT Collections

Organize NFTs into collections:
// Group NFTs by collection
function groupByCollection(nfts: NFTInfo[]) {
  return nfts.reduce((groups, nft) => {
    const collection = nft.collection || 'Uncategorized';
    if (!groups[collection]) {
      groups[collection] = [];
    }
    groups[collection].push(nft);
    return groups;
  }, {} as Record<string, NFTInfo[]>);
}

// Display collections
function CollectionView() {
  const { nfts } = useWeb3Store();
  const collections = groupByCollection(nfts);
  
  return (
    <div>
      {Object.entries(collections).map(([name, nfts]) => (
        <div key={name}>
          <h2>{name} ({nfts.length})</h2>
          <div className="nft-grid">
            {nfts.map(nft => (
              <NFTCard key={nft.mint} nft={nft} />
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

Using NFTs as Game Items

NFT attributes can determine in-game item stats:
function equipNFTAsItem(nft: NFTInfo) {
  // Parse attributes into game stats
  const stats = {
    attack: 0,
    defense: 0,
    speed: 0
  };
  
  nft.attributes?.forEach(attr => {
    if (attr.trait_type === 'Attack') {
      stats.attack = parseInt(attr.value);
    }
    if (attr.trait_type === 'Defense') {
      stats.defense = parseInt(attr.value);
    }
    if (attr.trait_type === 'Speed') {
      stats.speed = parseInt(attr.value);
    }
  });
  
  // Apply to player character
  player.equipment.weapon = {
    nftMint: nft.mint,
    name: nft.name,
    image: nft.imageUri,
    stats: stats
  };
}

NFT Metadata Standards

NFT metadata should follow standards for compatibility:

Solana (Metaplex)

{
  "name": "Legendary Dragon Sword #1",
  "symbol": "SWORD",
  "description": "A legendary sword forged from dragon scales",
  "image": "https://cdn.game.com/sword.png",
  "attributes": [
    { "trait_type": "Rarity", "value": "Legendary" },
    { "trait_type": "Attack", "value": "100" },
    { "trait_type": "Element", "value": "Fire" }
  ],
  "properties": {
    "category": "image",
    "files": [
      {
        "uri": "https://cdn.game.com/sword.png",
        "type": "image/png"
      }
    ]
  }
}

EVM (OpenSea Standard)

{
  "name": "Legendary Dragon Sword #1",
  "description": "A legendary sword forged from dragon scales",
  "image": "https://cdn.game.com/sword.png",
  "attributes": [
    { "trait_type": "Rarity", "value": "Legendary" },
    { "trait_type": "Attack", "value": 100, "display_type": "number" },
    { "trait_type": "Element", "value": "Fire" }
  ]
}
Upload NFT metadata to IPFS or Arweave for permanent, decentralized storage.

Adding NFTs to State

Manage NFT state in your game:
const web3Store = useWeb3Store();

// Add a newly minted NFT
web3Store.addNft({
  mint: "NewNFTMintAddress",
  name: "Legendary Sword",
  imageUri: "https://...",
  collection: "Game Items",
  attributes: [
    { trait_type: "Rarity", value: "Legendary" }
  ]
});

// Update all NFTs (e.g., after fetching from blockchain)
web3Store.setNfts(fetchedNFTs);

Checking NFT Rarity

Determine how rare an NFT is:
function calculateRarity(nft: NFTInfo, allNFTs: NFTInfo[]): number {
  // Count NFTs with same rarity
  const rarityAttr = nft.attributes?.find(a => a.trait_type === 'Rarity');
  if (!rarityAttr) return 0;
  
  const sameRarity = allNFTs.filter(n => 
    n.attributes?.some(a => 
      a.trait_type === 'Rarity' && a.value === rarityAttr.value
    )
  ).length;
  
  // Return rarity score (lower = more rare)
  return sameRarity / allNFTs.length;
}

Transaction Tracking

Track NFT mints and transfers:
export interface Web3Transaction {
  id: string;
  type: 'mint_nft' | 'transfer' | /* other types */;
  signature: string;
  status: 'pending' | 'confirmed' | 'failed';
  description: string;
  timestamp: number;
}

const { transactions } = useWeb3Store();

// Show NFT transactions
transactions
  .filter(tx => tx.type === 'mint_nft')
  .forEach(tx => {
    console.log(`NFT Mint: ${tx.status} - ${tx.description}`);
  });

Best Practices

Do’s

  • Use high-quality images (at least 512x512px)
  • Include meaningful attributes
  • Organize NFTs into collections
  • Store metadata on IPFS/Arweave
  • Make attributes useful in gameplay

Don’ts

  • Don’t mint too many NFTs too quickly (spam)
  • Don’t use copyrighted images without permission
  • Don’t make NFTs required for basic gameplay
  • Don’t promise NFTs will increase in value
  • Don’t link to images on servers you don’t control
NFTs are permanent and visible on the blockchain. Make sure images and metadata are appropriate and legal before minting.

Advanced: Dynamic NFTs

NFTs whose attributes change based on gameplay:
// Store changeable data off-chain, reference it in metadata
const dynamicNFT = {
  mint: "StaticMintAddress",
  name: "Growing Pet",
  imageUri: "https://api.game.com/nft/dynamic/123",  // Dynamic endpoint
  attributes: [
    { trait_type: "Level", value: "1" },           // Updated off-chain
    { trait_type: "XP", value: "0" }               // Updated off-chain
  ]
};

// API endpoint returns current state
// GET /nft/dynamic/123
// {
//   "name": "Growing Pet",
//   "image": "https://cdn.game.com/pet-level-5.png",  // Changes with level
//   "attributes": [
//     { "trait_type": "Level", "value": "5" },
//     { "trait_type": "XP", "value": "1250" }
//   ]
// }

Next Steps

Token Management

Learn about fungible tokens

Solana Integration

Technical details for Solana NFTs

Build docs developers (and LLMs) love