Игровой 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. Обращайтесь за консультацией.







