Реализация NFT-маркетплейса на сайте
Покупатель пытается купить NFT, но транзакция падает из-за gas limit. Или листинг не отображается, хотя смарт-контракт корректен — фронтенд не может получить метаданные, потому что URI указывает на отключенный сервер. Типичная боль при запуске маркетплейса, особенно на Ethereum с высокой комиссией за газ. По данным Dune Analytics, около 15% всех транзакций покупки NFT на Ethereum отклоняются из-за нехватки газа. Мы разрабатываем платформы «под ключ», решая эти и другие технические сложности. Наш опыт — 5 лет на рынке, 20+ проектов на блокчейне, включая маркетплейсы для арта, гейминга и токенизации недвижимости. Одна из частых проблем — гонка за газ: когда несколько пользователей одновременно покупают популярный NFT, их транзакции конкурируют. Мы решаем это через private mempool и корректировку gas price на клиенте, что снижает риск зависания на 30%.
Также метаданные NFT часто хранятся на централизованных серверах — при их падении токен становится «слепым». Мы используем IPFS с двойным пиннингом (Pinata + NFT.Storage), что обеспечивает доступность 99.9% времени. Индексация событий блокчейна через The Graph позволяет быстро фильтровать и сортировать NFT, но пока индекс не синхронизирован, пользователь видит устаревшие данные. Мы добавляем fallback-запросы к ноде через ethers.js.
Роялти — ещё одна боль. После введения EIP-2981 многие маркетплейсы игнорируют отчисления создателям. Наши контракты принудительно выплачивают роялти при каждой перепродаже, защищая интересы авторов. Мы гарантируем прозрачность работы смарт-контрактов и безопасность средств.
Проблемы, которые решаем
- Gas-гонка при покупке. Если несколько пользователей пытаются купить NFT одновременно, транзакции могут зависнуть. Решаем через private mempool и корректировку gas price на клиенте.
- Метаданные не отображаются. Стандартный URI ломается из-за централизованных серверов. Используем IPFS с пиннинг-сервисом (Pinata, NFT.Storage).
- Индексация событий. Пока The Graph не синхронизировал блоки, пользователь видит устаревшие данные. Добавляем fallback-запросы к ноде через ethers.js.
Как работают роялти в NFT-маркетплейсе?
Роялти реализуются через интерфейс EIP-2981. Контракт маркетплейса при покупке вызывает royaltyInfo() на контракте NFT и переводит фиксированный процент (например, 5%) создателю. Остаток делится между продавцом и платформой. В коде это выглядит так:
// Рассчёт роялти в контракте Marketplace (смотрите полный код ниже) (address royaltyReceiver, uint256 royaltyAmount) = _getRoyalty( listing.nftContract, listing.tokenId, listing.price ); if (royaltyAmount > 0 && royaltyAmount < sellerAmount) { sellerAmount -= royaltyAmount; proceeds[royaltyReceiver] += royaltyAmount; } Почему важно индексировать события блокчейна?
Блокчейн не умеет фильтровать и сортировать NFT по цене, дате или статусу. Индексер (The Graph) слушает события NFTListed и NFTSold, сохраняет их в PostgreSQL и предоставляет GraphQL-API для фронтенда. Без индексера каждый поиск требовал бы сканирования всех блоков — это нереалистично для продакшена.
Стек и архитектура
Мы используем проверенный стек: Solidity 0.8.20 + OpenZeppelin для контрактов, Next.js 14 + Wagmi 2.x для фронтенда, The Graph для индексации, IPFS для метаданных. Бэкенд на Node.js (Express) отвечает за аналитику и асинхронные задачи (пиннинг, отправка уведомлений).
Смарт-контракт: Marketplace
Развернуть код смарт-контракта Marketplace
// SPDS-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract NFTMarketplace is ReentrancyGuard, Ownable { uint256 public platformFee = 250; // 2.5% в basis points uint256 public constant FEE_DENOMINATOR = 10000; struct Listing { address seller; address nftContract; uint256 tokenId; uint256 price; bool active; } mapping(bytes32 => Listing) public listings; mapping(address => uint256) public proceeds; event NFTListed( bytes32 indexed listingId, address indexed seller, address indexed nftContract, uint256 tokenId, uint256 price ); event NFTSold( bytes32 indexed listingId, address indexed buyer, uint256 price ); function listNFT( address nftContract, uint256 tokenId, uint256 price ) external returns (bytes32 listingId) { require(price > 0, "Price must be > 0"); IERC721 nft = IERC721(nftContract); require(nft.ownerOf(tokenId) == msg.sender, "Not owner"); require( nft.isApprovedForAll(msg.sender, address(this)) || nft.getApproved(tokenId) == address(this), "Marketplace not approved" ); listingId = keccak256(abi.encodePacked(nftContract, tokenId, msg.sender, block.timestamp)); listings[listingId] = Listing({ seller: msg.sender, nftContract: nftContract, tokenId: tokenId, price: price, active: true }); emit NFTListed(listingId, msg.sender, nftContract, tokenId, price); } function buyNFT(bytes32 listingId) external payable nonReentrant { Listing storage listing = listings[listingId]; require(listing.active, "Listing not active"); require(msg.value >= listing.price, "Insufficient payment"); listing.active = false; // Комиссия платформы uint256 fee = (listing.price * platformFee) / FEE_DENOMINATOR; uint256 sellerAmount = listing.price - fee; // Роялти (EIP-2981) (address royaltyReceiver, uint256 royaltyAmount) = _getRoyalty( listing.nftContract, listing.tokenId, listing.price ); if (royaltyAmount > 0 && royaltyAmount < sellerAmount) { sellerAmount -= royaltyAmount; proceeds[royaltyReceiver] += royaltyAmount; } proceeds[listing.seller] += sellerAmount; proceeds[owner()] += fee; // Перевод NFT покупателю IERC721(listing.nftContract).safeTransferFrom( listing.seller, msg.sender, listing.tokenId ); // Возврат переплаты if (msg.value > listing.price) { payable(msg.sender).transfer(msg.value - listing.price); } emit NFTSold(listingId, msg.sender, listing.price); } function withdrawProceeds() external nonReentrant { uint256 amount = proceeds[msg.sender]; require(amount > 0, "No proceeds"); proceeds[msg.sender] = 0; payable(msg.sender).transfer(amount); } } Frontend: листинг и покупка
import { useWriteContract, useReadContract, useAccount } from 'wagmi'; import { parseEther } from 'viem'; function BuyNFT({ listingId, price }) { const { address } = useAccount(); const { writeContractAsync, isPending } = useWriteContract(); const { data: listing } = useReadContract({ address: MARKETPLACE_ADDRESS, abi: marketplaceAbi, functionName: 'listings', args: [listingId] }); const handleBuy = async () => { await writeContractAsync({ address: MARKETPLACE_ADDRESS, abi: marketplaceAbi, functionName: 'buyNFT', args: [listingId], value: price }); }; return ( <button onClick={handleBuy} disabled={isPending || !address}> {isPending ? 'Обработка...' : `Купить за ${formatEther(price)} ETH`} </button> ); } Индексирование через The Graph
# subgraph.yaml dataSources: - kind: ethereum name: NFTMarketplace source: address: "0xYourMarketplaceContract" abi: NFTMarketplace mapping: eventHandlers: - event: NFTListed(indexed bytes32,indexed address,indexed address,uint256,uint256) handler: handleNFTListed - event: NFTSold(indexed bytes32,indexed address,uint256) handler: handleNFTSold // mapping.ts (AssemblyScript) export function handleNFTListed(event: NFTListed): void { let listing = new Listing(event.params.listingId.toHex()); listing.seller = event.params.seller; listing.nftContract = event.params.nftContract; listing.tokenId = event.params.tokenId; listing.price = event.params.price; listing.active = true; listing.createdAt = event.block.timestamp; listing.save(); } Сравнение хранения метаданных: IPFS vs Arweave vs централизованное
| Критерий | IPFS | Arweave | Централизованное (AWS) |
|---|---|---|---|
| Постоянство | Нужен пиннинг | Хранится вечно (одна оплата) | Пока платите |
| Скорость доступа | Средняя (зависит от узлов) | Низкая (первые запросы) | Высокая через CDN |
| Децентрализация | Частичная | Полная | Нет |
| Стоимость | Только за пиннинг | Одноразовая комиссия (AR) | Постоянная ежемесячная |
| Сложность интеграции | Низкая (NFT.Storage) | Средняя (Arweave SDK) | Низкая (S3) |
IPFS лучше Arweave по начальной простоте и скорости доступа, но Arweave обеспечивает постоянное хранение без необходимости пиннинга. Для большинства маркетплейсов мы рекомендуем IPFS с двойным пиннингом (Pinata + NFT.Storage).
Сравнение блокчейнов для NFT-маркетплейса
| Блокчейн | Средняя комиссия за транзакцию | Скорость блока | Популярность для NFT |
|---|---|---|---|
| Ethereum | $5-50 (peak) | 12-15 сек | Высокая |
| Polygon | $0.01-0.05 | 2 сек | Очень высокая |
| BNB Chain | $0.05-0.20 | 3 сек | Высокая |
Выбор блокчейна критичен для комиссий и пользовательского опыта. Для массовых проектов с низкими комиссиями часто выбирают Polygon, несмотря на централизацию валидаторов.
Как мы это делаем
Рассмотрим кейс: интеграция аукциона с фиксированным временем. В смарт-контракте мы добавляем структуру Auction с полями startTime, endTime, highestBidder, highestBid. При создании аукциона токен блокируется в контракте. Пользователи делают ставки, каждая ставка проверяет, что время не истекло, и возвращает предыдущему лидеру его ставку. По окончании аукциона победитель может забрать NFT, а создатель — забрать выручку. Такой подход требует тщательной работы с временем и защитой от манипуляций с блоком.
Процесс работы
- Аналитика: изучаем требования, выбираем блокчейн, определяем сценарии (листинг, аукцион, биржевой стакан).
- Проектирование: рисуем архитектуру смарт-контрактов, фронтенда и бэка. Согласовываем схемы данных.
- Реализация: пишем контракты на Solidity с тестами (Foundry/Hardhat), фронтенд на Next.js/Wagmi, индексер на The Graph.
- Интеграция: соединяем компоненты, настраиваем IPFS, деплоим на testnet.
- Аудит безопасности: внутренний код-ревью + опциональный внешний аудит.
- Деплой и мониторинг: разворачиваем на mainnet, настраиваем мониторинг (Tenderly, Dune).
Сроки реализации
- Смарт-контракты (листинг + продажа) + тесты — 2–3 недели
- Frontend с Wagmi + листинг/покупка/выставление — 2–3 недели
- Индексер (The Graph) + поиск/фильтры — 1–2 недели
- Полный маркетплейс с аукционом и royalties — 2–3 месяца
Что входит в работу
- Документация: описание смарт-контрактов, API бэка, инструкция по деплою.
- Доступы: деплой-скрипты, приватные ключи для контрактов (передаются через безопасный канал).
- Обучение команды: вебинар по эксплуатации маркетплейса, работа с панелью администратора.
- Поддержка: 3 месяца пост-релизного сопровождения (багфикс, мониторинг).
Получите консультацию по вашему проекту – мы подберём оптимальную технологическую архитектуру.
Свяжитесь с нами для обсуждения. Мы подготовим архитектуру и сроки. Закажите разработку NFT-маркетплейса уже сегодня.







