Розробка 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
// SPDX-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% in 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, а творець — забрати виручку. Такий підхід вимагає ретельної роботи з часом та захистом від маніпуляцій з блоком. На одному з проектів ми впровадили цей механізм для маркетплейсу цифрового мистецтва, що дозволило збільшити середню ціну продажу на 40%.
Процес роботи
- Аналітика: вивчаємо вимоги, обираємо блокчейн, визначаємо сценарії (лістинг, аукціон, біржовий стакан).
- Проектування: малюємо архітектуру смарт-контрактів, фронтенду та бекенду. Узгоджуємо схеми даних.
- Реалізація: пишемо контракти на Solidity із тестами (Foundry/Hardhat), фронтенд на Next.js/Wagmi, індексатор на The Graph.
- Інтеграція: з'єднуємо компоненти, налаштовуємо IPFS, деплоїмо на testnet.
- Аудит безпеки: внутрішній код-рев'ю + опціональний зовнішній аудит.
- Деплой та моніторинг: розгортаємо на mainnet, налаштовуємо моніторинг (Tenderly, Dune).
Орієнтири за строками
- Смарт-контракти (лістинг + продаж) + тести — 2–3 тижні.
- Frontend з Wagmi + лістинг/покупка/виставлення — 2–3 тижні.
- Індексатор (The Graph) + пошук/фільтри — 1–2 тижні.
- Повний маркетплейс з аукціоном та роялті — 2–3 місяці.
Що входить у роботу
- Документація: опис смарт-контрактів, API бекенду, інструкція з деплою.
- Доступи: деплой-скрипти, приватні ключі для контрактів (передаються через безпечний канал).
- Навчання команди: вебінар з експлуатації маркетплейсу, робота з панеллю адміністратора.
- Підтримка: 3 місяці пост-релізного супроводу (багфікс, моніторинг).
Отримайте консультацію щодо вашого проекту – ми підберемо оптимальну технологічну архітектуру. Зв'яжіться з нами для обговорення. Ми підготуємо архітектуру та строки. Замовте розробку NFT-маркетплейсу вже сьогодні.







