Розробка внутрішньоігрового NFT-маркетплейсу під ключ
Ігровий NFT-маркетплейс — не просто вітрина JPEG. Якщо не продумати архітектуру смарт-контрактів та індексацію атрибутів, гравці зіткнуться із затримками транзакцій, неочікуваними комісіями та неможливістю торгувати предметами з унікальними характеристиками. На відміну від готових рішень, кастомна розробка дозволяє інтегрувати внутрішньоігрову валюту, оренду предметів та складні аукціони. Це знижує операційні витрати та утримує економіку всередині гри. Ми — Web3-розробники з 5+ річним досвідом — будуємо маркетплейси, які витримують навантаження сотень тисяч предметів з глибиною атрибутів (рівень, модифікатори, сумісність за класами). Отримайте консультацію, щоб обговорити ваш проєкт.
Які проблеми вирішує кастомний NFT-маркетплейс?
Готові рішення на кшталт Seaport (OpenSea protocol) покривають базові сценарії, але не враховують ігрову специфіку. Типові проблеми, які ми вирішуємо:
-
Gas-оптимізація. У грі можуть здійснюватися сотні мікротранзакцій на день. Кастомний контракт дозволяє використовувати пакетні трансфери ERC-1155 та агрегувати роялті в одну операцію. Ми використовуємо
batchTransferFromтаmultiCallдля зниження газу на 40-60%. Така економія складає від $0.01 до $0.05 за операцію, що при масовому використанні дає значний виграш. -
Reentrancy. Ігровий маркетплейс — часта мішень для атак повторного входу. Ми блокуємо їх за допомогою
ReentrancyGuardвід OpenZeppelin та перевіряємо за допомогою Echidna-фазінгу. - Складна логіка торгів. Bundle-продажі (персонаж + інвентар), аукціони з кількома валютами, лістинги з умовами (мінімальний рівень, класи). Seaport не підтримує таке без переписування.
Як вибрати стандарт токена та модель торгів?
Починаємо з вибору стандарту токена. Для ігор найкраще підходить ERC-1155 — один контракт на всі типи предметів, ERC-721 — для унікальних персонажів. Між ними варто обирати виходячи з очікуваної кількості типів предметів та частоти торгівлі. Також для оренди NFT використовуємо ERC-4907.
| Стандарт | Використання | Переваги |
|---|---|---|
| ERC-721 | Унікальні предмети (персонажі, легендарна зброя) | Кожен токен унікальний, легко відстежувати історію |
| ERC-1155 | Багато типів (скрині, витратні матеріали) | Економія газу, пакетні операції |
| ERC-4907 | Оренда предметів | Розподіл власності та права використання |
Потім визначаємо модель торгівлі:
- In-game currency vs ETH/USDC: Найкращий варіант — гібрид. Лістинги в ігровому токені, але кнопка «Купити за USDC» автоматично робить swap через DEX. Це утримує економіку в грі та приваблює зовнішню ліквідність.
- Роялті: розподіл між розробниками, творцями та стейкхолдерами. Реалізуємо через роздільний відсоток або стандарт ERC-2981.
- Оренда NFT: для дорогих предметів використовуємо ERC-4907, де ownership та right to use розділені.
Якщо ви хочете обговорити архітектуру вашого ігрового маркетплейсу, зв'яжіться з нами — ми допоможемо вибрати оптимальний стек.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GameNFTMarketplace {
IERC20 public gameToken;
IERC1155 public gameItems;
uint256 public marketFeePercent = 250; // 2.5%
uint256 public royaltyPercent = 500; // 5% до творців
address public treasury;
address public developersWallet;
struct Listing {
address seller;
uint256 itemTypeId;
uint256 amount;
uint256 pricePerUnit; // в gameToken
uint256 minimumPurchase; // мінімальна покупка
bool acceptsBundle; // чи приймає bundle оферти
uint256 expiresAt;
ListingType listingType;
}
enum ListingType { FIXED_PRICE, ENGLISH_AUCTION, DUTCH_AUCTION }
struct Auction {
address seller;
uint256 itemTypeId;
uint256 tokenId;
uint256 startPrice;
uint256 currentBid;
address currentBidder;
uint256 endTime;
uint256 minBidIncrement;
}
mapping(uint256 => Listing) public listings;
mapping(uint256 => Auction) public auctions;
// Fixed price purchase
function buyItem(uint256 listingId, uint256 amount) external {
Listing storage listing = listings[listingId];
require(listing.seller != address(0), "Listing not found");
require(block.timestamp <= listing.expiresAt, "Listing expired");
require(amount >= listing.minimumPurchase, "Below minimum purchase");
uint256 totalPrice = listing.pricePerUnit * amount;
uint256 fee = (totalPrice * marketFeePercent) / 10000;
uint256 royalty = (totalPrice * royaltyPercent) / 10000;
uint256 sellerProceeds = totalPrice - fee - royalty;
// Платежі
gameToken.transferFrom(msg.sender, listing.seller, sellerProceeds);
gameToken.transferFrom(msg.sender, treasury, fee);
gameToken.transferFrom(msg.sender, developersWallet, royalty);
// Передача предметів
listing.amount -= amount;
if (listing.amount == 0) delete listings[listingId];
gameItems.safeTransferFrom(listing.seller, msg.sender, listing.itemTypeId, amount, "");
emit ItemSold(listingId, msg.sender, amount, totalPrice);
}
// English auction
function placeBid(uint256 auctionId, uint256 bidAmount) external {
Auction storage auction = auctions[auctionId];
require(block.timestamp < auction.endTime, "Auction ended");
require(bidAmount >= auction.currentBid + auction.minBidIncrement, "Bid too low");
// Повернення попередньому bidder
if (auction.currentBidder != address(0)) {
gameToken.transfer(auction.currentBidder, auction.currentBid);
}
// Новий bid в escrow
gameToken.transferFrom(msg.sender, address(this), bidAmount);
auction.currentBid = bidAmount;
auction.currentBidder = msg.sender;
// Anti-snipe: якщо bid < 5 хвилин до кінця — продовжуємо
if (auction.endTime - block.timestamp < 5 minutes) {
auction.endTime += 5 minutes;
}
emit BidPlaced(auctionId, msg.sender, bidAmount);
}
// Dutch auction: ціна знижується з часом
function getDutchPrice(uint256 listingId) public view returns (uint256) {
Listing storage listing = listings[listingId];
// ... linearly interpolate price from startPrice to endPrice over duration
}
}
Як організувати пошук за атрибутами?
Ігровий маркетплейс вимагає багатого контексту: рівень предмета, модифікатори, сумісність з класом, історія боїв. Ми будуємо інтерфейс, де кожен NFT показується не як JPEG, а як повноцінна картка з характеристиками.
interface GameItemListing {
tokenId: number;
itemType: {
id: number;
name: string;
rarity: "common" | "rare" | "epic" | "legendary";
category: "weapon" | "armor" | "consumable" | "companion";
imageUrl: string;
};
attributes: {
level: number;
damage?: number;
defense?: number;
speed?: number;
durability: number;
upgradeCount: number;
enchantments: string[];
};
gameContext: {
compatibleClasses: string[];
compatibleGames: string[];
requiredLevel: number;
lastUsedInBattle?: Date;
totalBattlesUsed: number;
};
listing: {
price: bigint;
currency: "GGD" | "USDC";
seller: string;
listedAt: Date;
expiresAt: Date;
};
priceHistory: Array<{ price: bigint; date: Date }>;
floorPrice: bigint;
pricePower: number;
}
Для фільтрації за атрибутами використовуємо PostgreSQL з GIN-індексами на JSONB або Elasticsearch. Це дозволяє шукати предмети з конкретними зачаруваннями, рівнем або класом за мілісекунди.
interface MarketplaceFilters {
itemCategory?: string[];
rarities?: string[];
minPrice?: bigint;
maxPrice?: bigint;
minLevel?: number;
maxLevel?: number;
compatibleClass?: string;
hasEnchantment?: string;
currency?: "GGD" | "USDC";
sortBy?: "price_asc" | "price_desc" | "recently_listed" | "ending_soon" | "price_power";
}
async function searchListings(filters: MarketplaceFilters, page: number) {
const query = db("listings")
.where("status", "active")
.where("expires_at", ">", new Date());
if (filters.itemCategory?.length) {
query.whereIn("item_category", filters.itemCategory);
}
if (filters.minLevel) {
query.where("attributes->>'level'", ">=", filters.minLevel.toString());
}
if (filters.hasEnchantment) {
query.whereRaw("attributes->'enchantments' @> ?", [JSON.stringify([filters.hasEnchantment])]);
}
return query
.orderBy(getSortColumn(filters.sortBy))
.limit(PAGE_SIZE)
.offset(page * PAGE_SIZE);
}
Процес роботи
- Аналітика: розбираємо економіку гри, потік предметів, очікувані сценарії торгівлі.
- Проєктування: вибір L2 (Polygon, Arbitrum) для дешевих транзакцій, визначення структури контрактів.
- Реалізація: пишемо смарт-контракти з Foundry, фронтенд — Next.js + wagmi.
- Тестування: unit-тести, інтеграційні тести, fuzzing в Echidna, тести на gas consumption.
- Security-аудит: внутрішній Slither + зовнішній аудит за бажанням.
- Деплой: налаштування моніторингу Tenderly, індексація подій.
Напишіть нам, щоб отримати детальне комерційне пропозицію.
Що входить в роботу
- Смарт-контракти з повним покриттям тестами (95%+)
- Документація (архітектура, функції, події)
- Код фронтенду з кастомізованими картками
- Набір скриптів для деплою та верифікації на Etherscan
- Інтеграція з ігровим бекендом (REST або WebSocket)
- Навчання команди замовника роботі з адмінкою
- Підтримка протягом 1 місяця після запуску
Терміни орієнтовно
| Етап | Тривалість | Результат |
|---|---|---|
| Аналітика | 1–2 тижні | Технічне завдання, архітектура |
| Розробка контрактів | 4–6 тижнів | Вихідники, тести, документація |
| Фронтенд | 4–6 тижнів | UI з детальними картками, фільтрація |
| Інтеграція | 2–3 тижні | API для гри, імпорт предметів |
| Аудит та деплой | 3–4 тижні | Звіт аудиту, контракти в mainnet |
Вартість розраховується індивідуально. Оцінимо ваш проєкт — пишіть.
Ми гарантуємо, що маркетплейс пройде аудит та витримає навантаження до 10 000 одночасних користувачів. Досвід — 5+ років та 15+ реалізованих проєктів у DeFi та GameFi. Звертайтеся за консультацією.







