Розробка внутриігрової економіки на блокчейні
Внутриігрова економіка на блокчейні — це не просто «додати NFT в гру». Це проектування повноцінної економічної системи: виробництво, споживання, обмін, інфляція, дефляція, стимули. Неправильно спроектована економіка приводить до гіперінфляції ігрової валюти, exodus гравців і краху проекту (як сталося з Axie Infinity у 2022). Правильна — створює самопідтримуючуся екосистему.
Базові принципи ігрової економіки
Здорова ігрова економіка має баланс між sink (поглинувачами ресурсів) і faucet (джерелами ресурсів). Якщо джерел більше ніж поглинувачів — інфляція. Якщо поглинувачів більше — дефляція, гравці йдуть через недоступність контенту.
Faucet (джерела):
- Награди за перемоги в PvP/PvE
- Майнинг, фарму, крафтинг
- Staking rewards
- Daily quests
Sink (поглинувачі):
- Поліпшення предметів (спалює ресурси)
- Entry fees у арени/турніри
- Breeding/crafting стоимість
- Repair деградованих предметів
- Governance участь (стейкинг з lock)
Співвідношення faucet/sink повинно бути динамічно управляємим — залежно від реальних економічних показників можна включати/вимикати окремі механіки.
Токен-структура
Успішні blockchain-ігри використовують dual-token модель:
Governance token (наприклад, AXS у Axie):
- Обмежений supply
- Стейкинг для governance
- Розподіляється повільно, через активність і treasury
- Це «акція» гри
Utility/in-game token (наприклад, SLP у Axie):
- Нескінченний або м'яко-обмежений supply
- Заробляється в gameplay
- Витрачається на в-ігрові дії
- Це «валюта» гри
Помилка Axie Infinity: SLP можна було тільки заробляти (faucet), але спочатку було мало sink механік. Inflation знищила покупательну спроможність токена. З 2022 додали burn mechanics, але було пізно.
Смарт-контракти внутриігрової економіки
ERC-20 для ігрової валюти
// 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 щоб запобігти гіперінфляції
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);
}
// Ігровий сервер мінтить токени за перемоги/rewards
function mintReward(address player, uint256 amount) external onlyRole(MINTER_ROLE) {
// Скидання дневного ліміту
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 при in-game діях
function burnForAction(uint256 amount, bytes32 actionType) external {
_burn(msg.sender, amount);
emit ActionBurn(msg.sender, amount, actionType);
}
}
NFT для ігрових предметів (ERC-1155)
ERC-1155 кращий за ERC-721 для ігор: один контракт, кілька типів предметів, batch операції.
contract GameItems is ERC1155, AccessControl {
bytes32 public constant GAME_MASTER = keccak256("GAME_MASTER");
// Метаданні для кожного типу предмета
struct ItemType {
string name;
uint256 maxSupply;
uint256 currentSupply;
ItemRarity rarity;
bool tradeable; // деякі предмети не повинні торгуватися
bool upgradeable;
}
enum ItemRarity { COMMON, UNCOMMON, RARE, EPIC, LEGENDARY }
mapping(uint256 => ItemType) public itemTypes;
// Атрибути конкретного екземпляра (для 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: спалюємо матеріали та поліпшуємо предмет
function upgradeItem(
uint256 itemTypeId,
uint256 tokenId,
uint256[] calldata materialIds,
uint256[] calldata materialAmounts
) external {
// Спалюємо матеріали
_burnBatch(msg.sender, materialIds, materialAmounts);
// Збільшуємо атрибут предмета (випадковий буст)
uint256 boost = _calculateUpgradeBoost(itemTypeId);
tokenAttributes[itemTypeId][tokenId] += boost;
emit ItemUpgraded(msg.sender, itemTypeId, tokenId, boost);
}
}
Marketplace для внутриігрової торгівлі
contract GameMarketplace {
struct Listing {
address seller;
uint256 itemTypeId;
uint256 tokenId; // для ERC-721, 0 для ERC-1155
uint256 amount; // для ERC-1155
uint256 price; // в GameToken
uint256 expiresAt;
}
uint256 public marketplaceFee = 250; // 2.5% в basis points
address public treasury;
function listItem(
uint256 itemTypeId,
uint256 tokenId,
uint256 amount,
uint256 price,
uint256 duration
) external returns (uint256 listingId) {
// Escrow: переводимо NFT на 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;
// Оплата
gameToken.transferFrom(msg.sender, listing.seller, sellerProceeds);
gameToken.transferFrom(msg.sender, treasury, fee);
// Передача предмета
gameItems.safeTransferFrom(address(this), msg.sender, listing.itemTypeId, listing.amount, "");
delete listings[listingId];
emit Sold(listingId, msg.sender, listing.price);
}
}
Економічні механізми захисту від інфляції
Dynamic reward scaling
Чим більше гравців — тим менше reward за перемогу, щоб підтримати цінність:
function calculateReward(address player) external view returns (uint256) {
uint256 baseReward = BASE_DAILY_REWARD;
// Scaling за кількістю активних гравців
uint256 activePlayerCount = getActivePlayerCount();
if (activePlayerCount > REWARD_THRESHOLD) {
uint256 scalingFactor = (REWARD_THRESHOLD * 1e18) / activePlayerCount;
return (baseReward * scalingFactor) / 1e18;
}
return baseReward;
}
Burning mechanics через gameplay
Кожна ключова in-game операція спалює токени:
- Crafting item: burn 100 GGD + матеріали
- Entry у ranked match: burn 10 GGD (90% у prize pool, 10% burned)
- Respec character: burn 500 GGD
- Name change: burn 200 GGD
- Breeding: burn X GGD (зростаюча вартість з кожним breeding)
Anti-bot захист
Боти фармять токени швидше людей → дисбаланс. Захисти:
- Captcha на claim транзакціях
- Proof-of-gameplay (сервер генерує підписаний challenge за результатами ігрової сесії)
- Rate limiting: максимум N reward транзакцій у день з одної адреси
- VRF-based random в gameplay — результат нельзя передбачити заранее
Governance через DAO
Зміни економічних параметрів (mint rate, burn mechanics, fee levels) повинні проходити через governance:
contract GameDAO {
// Proposal для зміни економічних параметрів
function proposeEconomyChange(
address target,
bytes calldata calldata_,
string calldata description
) external {
require(governanceToken.balanceOf(msg.sender) >= PROPOSAL_THRESHOLD, "Insufficient tokens");
// ... створення proposal з timelock
}
}
Це дозволяє гравцям участь у налаштуванні економіки і створює skin-in-the-game для крупних тримачів токена.
Off-chain vs on-chain gameplay
Помилка — намагатися виконати всю ігрову логіку on-chain. Gas дорогий, блоки повільні, UX страждає.
On-chain зберігати:
- Володіння (хто чим володіє)
- Фінансові транзакції (торгівля, rewards)
- Random number generation (VRF)
- Governance decisions
Off-chain зберігати:
- Ігрову логіку і gameplay
- Стан матчів
- Проміжні результати (до claim)
- Metadata NFT (IPFS)
Паттерн: server підписує результат (гравець X переміг з результатом Y) → гравець подає підпис on-chain для claim reward. Server не може сфабрикувати перемоги (гравець верифікує підпис) але on-chain не потрібно знати правила гри.
Стек
| Компонент | Технологія |
|---|---|
| Smart contracts | Solidity + Foundry + OpenZeppelin |
| In-game token | ERC-20 з mint/burn roles |
| Items | ERC-1155 |
| Marketplace | Кастомний + Seaport compatibility |
| Random | Chainlink VRF v2.5 |
| Backend | Node.js + TypeScript |
| Frontend | Unity/Unreal + web3 plugin, або React |
| L2 | Arbitrum / Polygon PoS (низький gas) |
Сроки
- Токен-дизайн і документація: 2-3 тижні
- Smart contracts (token + items + marketplace): 6-8 тижнів
- Governance + DAO: 3-4 тижні
- Backend server (reward signing, anti-bot): 4-6 тижнів
- Security audit: обов'язковий, 4-6 тижнів
- Итого: 4-6 місяців







