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 should follow standards for compatibility:
{
"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