Пользователь хочет показать свои NFT на сайте, но прямое чтение из блокчейна — это медленно и больно. ERC-721 не имеет метода tokensOfOwner, приходится перебирать события или полагаться на ERC721Enumerable, который есть не у всех. Мы используем специализированные NFT API, которые индексируют события и возвращают готовые списки. Например, для коллекции BAYC вы получаете все токены по адресу владельца за один запрос, без перебора миллионов событий. Но есть подводные камни: IPFS-шлюзы, медленная загрузка, обработка ошибок. Наша реализация учитывает всё это.
Почему не читать NFT напрямую из контракта?
ERC-721 контракт хранит ownerOf(tokenId) и tokenURI(tokenId), но не предоставляет обратный маппинг. Без ERC721Enumerable найти все токены пользователя через RPC — значит перебрать все события Transfer за всю историю, или держать собственную базу. NFT API (Alchemy, Moralis, OpenSea) делают индексацию за нас — это быстрее и надёжнее. Альтернативный подход с собственным индексером потребует инфраструктуры и времени на разработку, что неоправданно для большинства проектов.
Как получать NFT через Alchemy API?
Мы используем alchemy.nft.getNftsForOwner с конфигурацией SDK.
// lib/nft.ts import { Alchemy, Network, OwnedNft } from 'alchemy-sdk'; const alchemy = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ETH_MAINNET, }); export interface NftItem { tokenId: string; contractAddress: string; name: string; description: string; imageUrl: string; collectionName: string; attributes: Array<{ trait_type: string; value: string | number }>; } export async function getWalletNfts( ownerAddress: string, opts?: { contractAddresses?: string[]; pageSize?: number }, ): Promise<NftItem[]> { const response = await alchemy.nft.getNftsForOwner(ownerAddress, { contractAddresses: opts?.contractAddresses, pageSize: opts?.pageSize ?? 100, omitMetadata: false, }); return response.ownedNfts.map(mapNft); } function mapNft(nft: OwnedNft): NftItem { const imageUrl = resolveIpfsUrl( nft.image?.cachedUrl ?? nft.image?.originalUrl ?? '', ); return { tokenId: nft.tokenId, contractAddress: nft.contract.address, name: nft.name ?? `#${nft.tokenId}`, description: nft.description ?? '', imageUrl, collectionName: nft.contract.name ?? 'Unknown Collection', attributes: (nft.raw?.metadata?.attributes ?? []) as NftItem['attributes'], }; } function resolveIpfsUrl(url: string): string { if (url.startsWith('ipfs://')) { return url.replace('ipfs://', 'https://cloudflare-ipfs.com/ipfs/'); } return url; } Пример ответа Alchemy API (сокращён)
{ "ownedNfts": [ { "contract": { "address": "0x..." }, "tokenId": "1", "tokenType": "ERC721", "title": "My NFT", "description": "...", "metadata": { "image": "ipfs://..." } } ], "pageKey": "...", "totalCount": 42 } Alchemy NFT API Documentation
Для реального времени можно подключить WebSocket-уведомления о новых токенах, что полезно для динамических коллекций.
Компонент галереи и карточки
Компонент галереи
Используем React Query для загрузки и кэширования данных.
// components/NftGallery.tsx import { useQuery } from '@tanstack/react-query'; import { getWalletNfts, NftItem } from '@/lib/nft'; interface NftGalleryProps { address: string; contractFilter?: string[]; } export function NftGallery({ address, contractFilter }: NftGalleryProps) { const { data, isLoading, error } = useQuery({ queryKey: ['nfts', address, contractFilter], queryFn: () => getWalletNfts(address, { contractAddresses: contractFilter }), staleTime: 60_000, // NFT меняются редко — кэшируем на минуту enabled: !!address, }); if (isLoading) return <NftGridSkeleton count={12} />; if (error) return <ErrorState message="Не удалось загрузить NFT" />; if (!data?.length) return <EmptyState />; return ( <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4"> {data.map(nft => ( <NftCard key={`${nft.contractAddress}-${nft.tokenId}`} nft={nft} /> ))} </div> ); } Карточка и детальный просмотр
// components/NftCard.tsx import { useState } from 'react'; import { NftItem } from '@/lib/nft'; export function NftCard({ nft }: { nft: NftItem }) { const [imgError, setImgError] = useState(false); return ( <div className="group relative overflow-hidden rounded-xl border border-white/10 bg-neutral-900"> <div className="aspect-square overflow-hidden bg-neutral-800"> {imgError ? ( <div className="flex h-full items-center justify-center text-neutral-500"> <ImageIcon className="h-12 w-12" /> </div> ) : ( <img src={nft.imageUrl} alt={nft.name} loading="lazy" decoding="async" className="h-full w-full object-cover transition-transform group-hover:scale-105" onError={() => setImgError(true)} /> )} </div> <div className="p-3"> <p className="truncate text-xs text-neutral-400">{nft.collectionName}</p> <p className="mt-0.5 truncate font-medium text-white">{nft.name}</p> </div> </div> ); } // components/NftDetail.tsx export function NftDetail({ nft }: { nft: NftItem }) { return ( <div className="space-y-6"> <img src={nft.imageUrl} alt={nft.name} className="w-full rounded-2xl" /> <div> <h2 className="text-2xl font-bold">{nft.name}</h2> <p className="mt-1 text-sm text-neutral-400"> {nft.collectionName} · #{nft.tokenId} </p> </div> {nft.description && ( <p className="text-sm leading-relaxed text-neutral-300">{nft.description}</p> )} {nft.attributes.length > 0 && ( <div> <h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-neutral-500"> Атрибуты </h3> <div className="grid grid-cols-3 gap-2"> {nft.attributes.map((attr, i) => ( <div key={i} className="rounded-lg border border-blue-500/20 bg-blue-500/5 p-2 text-center"> <p className="text-xs text-blue-400">{attr.trait_type}</p> <p className="mt-0.5 text-sm font-medium">{attr.value}</p> </div> ))} </div> </div> )} </div> ); } Такая структура позволяет легко встраивать компоненты в любой React-проект.
Поддержка ERC-1155 и пагинация
Для ERC-1155 токенов Alchemy возвращает balance. Мы проверяем tokenType и добавляем поле quantity. Для больших коллекций реализуем пагинацию через pageKey.
// utils/nft-extras.ts export function filterErc1155WithBalance( nfts: OwnedNft[], mapNft: (nft: OwnedNft) => NftItem, ) { return nfts .filter(nft => nft.tokenType === 'ERC1155') .map(nft => ({ ...mapNft(nft), quantity: nft.balance, })); } export async function getAllWalletNfts( ownerAddress: string, ): Promise<NftItem[]> { const all: NftItem[] = []; let pageKey: string | undefined; do { const response = await alchemy.nft.getNftsForOwner(ownerAddress, { pageKey, pageSize: 100, }); all.push(...response.ownedNfts.map(mapNft)); pageKey = response.pageKey; } while (pageKey); return all; } Сравнение решений
Сравнение NFT API
| Критерий | Alchemy | Moralis | OpenSea |
|---|---|---|---|
| Мультичейн | Ethereum, Polygon, Optimism, Arbitrum | 20+ сетей | Ethereum, Polygon, Klaytn |
| Бесплатный лимит | 100k/мес | 40k/мес | 10k/мес |
| Тип токенов | ERC-721, ERC-1155 | ERC-721, ERC-1155 | ERC-721, ERC-1155 |
| Метаданные | Автоматически | Частично | Требуется own |
| Пагинация | pageKey | cursor | next |
RPC vs NFT API
| Аспект | Чтение через RPC | NFT API |
|---|---|---|
| Скорость | Медленно (перебор событий) | Быстро (индексированные данные) |
| Простота | Сложно (нужна свою база) | Просто (один endpoint) |
| Поддержка ERC-1155 | Нужно обрабатывать отдельно | Встроена |
| IPFS изображения | Нужно кешировать | Автоматически проксируются |
Этапы работы
- Анализ — определяем, какие сети и контракты нужны, согласовываем API.
- Интеграция — подключаем SDK, реализуем загрузку и отображение.
- Оптимизация — lazy-загрузка, кэширование, обработка ошибок.
- Тестирование — проверяем на реальных кошельках с большими коллекциями.
- Деплой — разворачиваем на продакшн, настраиваем мониторинг.
Что входит в работу
- Документация по интеграции и настройке API-ключей.
- Исходный код компонентов на React/TypeScript.
- Инструкция по деплою и обновлению.
- Тестовый доступ к работающему прототипу.
- Поддержка в течение 2 недель после сдачи.
Сроки и стоимость
Базовая галерея с Alchemy API, lazy-загрузкой и детальным просмотром — 1–2 дня. Расширенная версия с фильтрацией по коллекциям, пагинацией, поддержкой ERC-1155 и мультичейн — 3–4 дня. Стоимость рассчитывается индивидуально в зависимости от сложности и стека. Для примера: при самостоятельной разработке инфраструктура и время могут стоить более $5000, наше решение обходится в разы дешевле. Свяжитесь с нами для оценки вашего проекта — мы подберём оптимальное решение.
Частые ошибки при интеграции
-
Необработанный IPFS — изображения не загружаются, если не использовать шлюз. Всегда заменяйте
ipfs://наhttps://cloudflare-ipfs.com/ipfs/. - Игнорирование balance у ERC-1155 — может привести к некорректному отображению количества. Всегда проверяйте
tokenType. - Слишком короткий staleTime — NFT меняются редко, кэширование на 1–5 минут существенно снижает нагрузку на API.
- Отсутствие fallback для изображений — при ошибке загрузки показывайте заглушку, иначе пользователь видит битую иконку.
Для добавления поиска по NFT и фильтрации по коллекциям мы интегрируем стейт на фронтенде или используем серверный поиск через API Alchemy. Это позволяет быстро находить нужные токены и сортировать по коллекциям.
Гарантируем совместимость с ведущими коллекциями и многолетний опыт интеграции NFT API. Мы разрабатываем с 2018 года и реализовали более 10 проектов с NFT-отображением для маркетплейсов и крипто-портфолио. Получите консультацию — закажите интеграцию под ваш проект. Использование Alchemy API позволяет сэкономить до $1000 в месяц на серверах и индексации.







