Пользователь хочет показать свои 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 в месяц на серверах и индексации.







