הצגת אוסף ה-NFT של משתמש באתר

הצגת אוסף NFT באתר נתקלת לא פעם בקריאות בלוקצ'יין איטיות ובקשיים באיתור הטוקנים של הבעלים. אנו מיישמים את התצוגה באמצעות ממשקי NFT ייעודיים שמחזירים במהירות רשימות נכסים מוכנות. הצוות שלנו מטפל בכל המחזור—מהאינטגרציה ועד לטיפול בשגיאות—ומבטיח גלריה אמינה עם תמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
הצגת אוסף ה-NFT של משתמש באתר
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

הצגת אוסף ה-NFT של משתמש באתר

המשתמש רוצה להציג את ה-NFTים שלו באתר, אבל קריאות ישירות לבלוקצ'יין איטיות ומסורבלות. ל-ERC-721 אין מתודת tokensOfOwner; צריך לעבור על אירועים או להסתמך על ERC721Enumerable, שלא כל חוזה מיישם. אנחנו משתמשים ב-API ייעודיים ל-NFT שמתעדים אירועים ומחזירים רשימות מוכנות. לדוגמה, עבור אוסף BAYC, מקבלים את כל הטוקנים עבור כתובת בעלים בבקשה אחת, בלי לעבור על מיליוני אירועים. אבל יש מלכודות: שערי IPFS, טעינה איטית, טיפול בשגיאות. היישום שלנו מתחשב בכל זה.

למה לא לקרוא NFTים ישירות מהחוזה?

חוזי ERC-721 מאחסנים ownerOf(tokenId) ו-tokenURI(tokenId), אבל לא מספקים מיפוי הפוך. בלי ERC721Enumerable, מציאת כל הטוקנים של משתמש דרך RPC דורשת מעבר על כל אירועי Transfer בהיסטוריה או ניהול מסד נתונים משלך. APIים ל-NFT (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

לעדכונים בזמן אמת, אפשר לחבר התראות WebSocket על טוקנים חדשים, מה ששימושי לאוספים דינמיים.

רכיבי גלריה וכרטיס

רכיב גלריה

אנחנו משתמשים ב-React Query להבאת וקaching נתונים.

// 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 מחזיר // 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; } . אנחנו בודקים { "ownedNfts": [ { "contract": { "address": "0x..." }, "tokenId": "1", "tokenType": "ERC721", "title": "My NFT", "description": "...", "metadata": { "image": "ipfs://..." } } ], "pageKey": "...", "totalCount": 42 } ומוסיפים שדה // 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> ); } .

// 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;
}

השוואת פתרונות

השוואת APIים ל-NFT

קריטריון 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
מטא-דאטה אוטומטי חלקי דורש משלך
פאג'ינציה pageKey cursor next

RPC לעומת NFT API

היבט קריאה דרך RPC NFT API
מהירות איטית (מעבר על אירועים) מהירה (נתונים מתועדים)
מורכבות קשה (נדרש מסד נתונים משלך) פשוטה (נקודת קצה אחת)
תמיכה ב-ERC-1155 צריך לטפל בנפרד מובנית
תמונות IPFS צריך caching מתווכות אוטומטית

שלבי עבודה

  1. ניתוח — קביעה אילו רשתות וחוזים נדרשים, הסכמה על API.
  2. אינטגרציה — חיבור SDK, יישום הבאה והצגה.
  3. אופטימיזציה — טעינה עצלה, caching, טיפול בשגיאות.
  4. בדיקות — אימות על ארנקים אמיתיים עם אוספים גדולים.
  5. פריסה — פריסה לייצור, הגדרת ניטור.

מה כלול

  • תיעוד על אינטגרציה והגדרת מפתח API.
  • קוד מקור של רכיבי React/TypeScript.
  • הוראות פריסה ועדכון.
  • גישת בדיקה לאב-טיפוס עובד.
  • תמיכה למשך שבועיים לאחר המסירה.

לוחות זמנים ועלות

גלריה בסיסית עם Alchemy API, טעינה עצלה ותצוגת פרטים — 1–2 ימים. גרסה מורחבת עם פילטרים לאוספים, פאג'ינציה, תמיכה ב-ERC-1155 וריבוי רשתות — 3–4 ימים. העלות מחושבת באופן אישי לפי מורכבות וטכנולוגיה. לדוגמה, תשתית וזמן פיתוח עצמיים יכולים לעלות מעל $5,000; הפתרון שלנו זול משמעותית. צרו קשר להערכת פרויקט — נמצא את הפתרון האופטימלי.

טעויות אינטגרציה נפוצות

  • IPFS ללא טיפול — תמונות לא נטענות בלי שער. תמיד החליפו balance ב-tokenType.
  • התעלמות מ-balance עבור ERC-1155 — עלולה להוביל להצגה שגויה של כמות. תמיד בדקו quantity.
  • staleTime קצר מדי — NFTים משתנים לעיתים רחוקות; caching למשך 1–5 דקות מפחית משמעותית את עומס ה-API.
  • חוסר fallback לתמונות — הציגו placeholder בשגיאה, אחרת המשתמש רואה אייקון שבור.

להוספת חיפוש NFT וסינון לפי אוספים, אנחנו משלבים state בצד הלקוח או משתמשים בחיפוש בצד השרת דרך Alchemy API. זה מאפשר מציאה מהירה של טוקנים רצויים ומיון לפי אוספים.

אנחנו מבטיחים תאימות לאוספים מובילים וניסיון רב שנים באינטגרציה של APIים ל-NFT. אנחנו מפתחים מאז 2018 ויישמנו מעל 10 פרויקטים עם הצגת NFT עבור מרקטפלייסים ותיקי קריפטו. קבלו ייעוץ — הזמינו אינטגרציה לפרויקט שלכם. שימוש ב-Alchemy API יכול לחסוך עד $1,000 בחודש על שרתים ואינדוקס.