Разработка внутриигровой экономики на блокчейне
Внутриигровая экономика на блокчейне — это не просто «добавить NFT в игру». Это проектирование полноценной экономической системы: производство, потребление, обмен, инфляция, дефляция, стимулы. Неправильно спроектированная экономика приводит к гиперинфляции игровой валюты, exodus игроков и краху проекта (как произошло с Axie Infinity в 2022). Правильная — создаёт самоподдерживающуюся экосистему.
Базовые принципы игровой экономики
Здоровая игровая экономика имеет баланс между sink (поглотители ресурсов) и faucet (источники ресурсов). Если источников больше чем поглотителей — инфляция. Если поглотителей больше — дефляция, игроки уходят из-за недоступности контента.
Faucet (источники):
- Награды за победы в PvP/PvE
- Майнинг, фарминг, крафтинг
- Staking rewards
- Daily quests
Sink (поглотители):
- Улучшение предметов (сжигает ресурсы)
- Entry fees в арены/турниры
- Breeding/crafting стоимость
- Repair degraded items
- 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 для 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 чтобы предотвратить гиперинфляцию
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);
}
// Сервер игры минтит токены за victories/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 operations.
contract GameItems is ERC1155, AccessControl {
bytes32 public constant GAME_MASTER = keccak256("GAME_MASTER");
// Metadata для каждого типа предмета
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) {
// Эскроу: переводим 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);
}
}
Экономические механизмы защиты от inflation
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% goes to 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 (сервер генерирует signed 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. Сервер не может сфабриковать победы (игрок верифицирует подпись) но и 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 (низкий газ) |
Сроки
- Токен-дизайн и документация: 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 месяцев







