In-Game Economy Development on Blockchain
In-game economy on blockchain is not simply "add NFT to the game." It's designing a full economic system: production, consumption, exchange, inflation, deflation, incentives. Incorrectly designed economy leads to in-game currency hyperinflation, player exodus, and project collapse (as happened with Axie Infinity in 2022). Correct design creates a self-sustaining ecosystem.
Basic Principles of Game Economics
Healthy game economy has balance between sink (resource consumers) and faucet (resource sources). If sources exceed consumers—inflation. If consumers exceed sources—deflation, players leave due to content inaccessibility.
Faucet (sources):
- Rewards for PvP/PvE victories
- Mining, farming, crafting
- Staking rewards
- Daily quests
Sink (consumers):
- Item improvements (burns resources)
- Entry fees for arenas/tournaments
- Breeding/crafting cost
- Repair degraded items
- Governance participation (staking with lock)
The faucet/sink ratio must be dynamically manageable—depending on actual economic metrics, you can enable/disable individual mechanics.
Token Structure
Successful blockchain games use dual-token model:
Governance token (e.g., AXS in Axie):
- Limited supply
- Staking for governance
- Distributed slowly, through activity and treasury
- This is the "stock" of the game
Utility/in-game token (e.g., SLP in Axie):
- Infinite or soft-limited supply
- Earned in gameplay
- Spent on in-game actions
- This is the "currency" of the game
Axie Infinity's mistake: SLP could only be earned (faucet), but initially had few sink mechanics. Inflation killed token purchasing power. From 2022 they added burn mechanics, but it was too late.
Smart Contracts for In-Game Economy
ERC-20 for In-Game Currency
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract GameToken is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
// Mint limits to prevent hyperinflation
uint256 public maxDailyMint;
uint256 public dailyMinted;
uint256 public lastMintReset;
constructor(uint256 _maxDailyMint) ERC20("GameGold", "GGD") {
maxDailyMint = _maxDailyMint;
lastMintReset = block.timestamp;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// Game server mints tokens for victories/rewards
function mintReward(address player, uint256 amount) external onlyRole(MINTER_ROLE) {
// Reset daily limit
if (block.timestamp >= lastMintReset + 1 days) {
dailyMinted = 0;
lastMintReset = block.timestamp;
}
require(dailyMinted + amount <= maxDailyMint, "Daily mint limit exceeded");
dailyMinted += amount;
_mint(player, amount);
emit RewardMinted(player, amount);
}
// Burn on in-game actions
function burnForAction(uint256 amount, bytes32 actionType) external {
_burn(msg.sender, amount);
emit ActionBurn(msg.sender, amount, actionType);
}
}
NFT for Game Items (ERC-1155)
ERC-1155 is preferable to ERC-721 for games: one contract, multiple item types, batch operations.
contract GameItems is ERC1155, AccessControl {
bytes32 public constant GAME_MASTER = keccak256("GAME_MASTER");
// Metadata for each item type
struct ItemType {
string name;
uint256 maxSupply;
uint256 currentSupply;
ItemRarity rarity;
bool tradeable; // some items shouldn't be traded
bool upgradeable;
}
enum ItemRarity { COMMON, UNCOMMON, RARE, EPIC, LEGENDARY }
mapping(uint256 => ItemType) public itemTypes;
// Attributes of specific instance (for RPG)
mapping(uint256 => mapping(uint256 => uint256)) public tokenAttributes;
// itemId => tokenId => attributeValue
function mintItem(
address player,
uint256 itemTypeId,
uint256 amount,
bytes calldata data
) external onlyRole(GAME_MASTER) {
ItemType storage item = itemTypes[itemTypeId];
require(item.currentSupply + amount <= item.maxSupply, "Max supply reached");
item.currentSupply += amount;
_mint(player, itemTypeId, amount, data);
}
// Upgrade: burn materials and improve item
function upgradeItem(
uint256 itemTypeId,
uint256 tokenId,
uint256[] calldata materialIds,
uint256[] calldata materialAmounts
) external {
// Burn materials
_burnBatch(msg.sender, materialIds, materialAmounts);
// Increase item attribute (random boost)
uint256 boost = _calculateUpgradeBoost(itemTypeId);
tokenAttributes[itemTypeId][tokenId] += boost;
emit ItemUpgraded(msg.sender, itemTypeId, tokenId, boost);
}
}
Marketplace for In-Game Trading
contract GameMarketplace {
struct Listing {
address seller;
uint256 itemTypeId;
uint256 tokenId; // for ERC-721, 0 for ERC-1155
uint256 amount; // for ERC-1155
uint256 price; // in GameToken
uint256 expiresAt;
}
uint256 public marketplaceFee = 250; // 2.5% in basis points
address public treasury;
function listItem(
uint256 itemTypeId,
uint256 tokenId,
uint256 amount,
uint256 price,
uint256 duration
) external returns (uint256 listingId) {
// Escrow: transfer NFT to marketplace
gameItems.safeTransferFrom(msg.sender, address(this), itemTypeId, amount, "");
listingId = ++_listingCounter;
listings[listingId] = Listing({
seller: msg.sender,
itemTypeId: itemTypeId,
tokenId: tokenId,
amount: amount,
price: price,
expiresAt: block.timestamp + duration,
});
emit Listed(listingId, msg.sender, itemTypeId, amount, price);
}
function buyItem(uint256 listingId) external {
Listing storage listing = listings[listingId];
require(listing.seller != address(0), "Listing not found");
require(block.timestamp <= listing.expiresAt, "Listing expired");
uint256 fee = (listing.price * marketplaceFee) / 10000;
uint256 sellerProceeds = listing.price - fee;
// Payment
gameToken.transferFrom(msg.sender, listing.seller, sellerProceeds);
gameToken.transferFrom(msg.sender, treasury, fee);
// Transfer item
gameItems.safeTransferFrom(address(this), msg.sender, listing.itemTypeId, listing.amount, "");
delete listings[listingId];
emit Sold(listingId, msg.sender, listing.price);
}
}
Economic Mechanisms Against Inflation
Dynamic Reward Scaling
More players—lower reward per victory to maintain value:
function calculateReward(address player) external view returns (uint256) {
uint256 baseReward = BASE_DAILY_REWARD;
// Scaling by active players
uint256 activePlayerCount = getActivePlayerCount();
if (activePlayerCount > REWARD_THRESHOLD) {
uint256 scalingFactor = (REWARD_THRESHOLD * 1e18) / activePlayerCount;
return (baseReward * scalingFactor) / 1e18;
}
return baseReward;
}
Burning Mechanics Through Gameplay
Each key in-game operation burns tokens:
- Craft item: burn 100 GGD + materials
- Entry to ranked match: burn 10 GGD (90% to prize pool, 10% burned)
- Respec character: burn 500 GGD
- Name change: burn 200 GGD
- Breeding: burn X GGD (increasing cost with each breeding)
Anti-bot Protection
Bots farm tokens faster than humans → imbalance. Protections:
- Captcha on claim transactions
- Proof-of-gameplay (server generates signed challenge from game session results)
- Rate limiting: max N reward transactions per day from one address
- VRF-based random in gameplay—result can't be predicted in advance
Governance Through DAO
Changes to economic parameters (mint rate, burn mechanics, fee levels) should go through governance:
contract GameDAO {
// Proposal for economic parameter changes
function proposeEconomyChange(
address target,
bytes calldata calldata_,
string calldata description
) external {
require(governanceToken.balanceOf(msg.sender) >= PROPOSAL_THRESHOLD, "Insufficient tokens");
// ... create proposal with timelock
}
}
This allows players to participate in economy tuning and creates skin-in-the-game for major token holders.
Off-chain vs On-chain Gameplay
Mistake—trying to execute all game logic on-chain. Gas is expensive, blocks are slow, UX suffers.
Store on-chain:
- Ownership (who owns what)
- Financial transactions (trading, rewards)
- Random number generation (VRF)
- Governance decisions
Store off-chain:
- Game logic and gameplay
- Match state
- Intermediate results (until claim)
- NFT metadata (IPFS)
Pattern: server signs result (player X won with score Y) → player presents signature on-chain to claim reward. Server can't fabricate victories (player verifies signature) but on-chain doesn't need to know game rules.
Stack
| Component | Technology |
|---|---|
| Smart contracts | Solidity + Foundry + OpenZeppelin |
| In-game token | ERC-20 with mint/burn roles |
| Items | ERC-1155 |
| Marketplace | Custom + Seaport compatibility |
| Random | Chainlink VRF v2.5 |
| Backend | Node.js + TypeScript |
| Frontend | Unity/Unreal + web3 plugin, or React |
| L2 | Arbitrum / Polygon PoS (low gas) |
Timeline
- Token design and documentation: 2-3 weeks
- Smart contracts (token + items + marketplace): 6-8 weeks
- Governance + DAO: 3-4 weeks
- Backend server (reward signing, anti-bot): 4-6 weeks
- Security audit: mandatory, 4-6 weeks
- Total: 4-6 months







