Skip to main content
Because the collection is the source of truth, verification goes through it:
  1. Read the NFT item index from the item contract via get_nft_data().
  2. Query the collection with get_nft_address_by_index(index).
  3. If the returned address equals the NFT item address, the item belongs to the collection.

High level

There is an API method that performs this verification off‑chain with a single request. Provide the addresses of the NFT item and NFT collection:
TypeScript
async function main() {
  const itemAddress = "EQD3LzasMd4GAmhIEkCQ4k6LnziTqNZ6VPtRfeZKHu0Fmkho";
  const collectionAddress = "EQCOtGTvX-RNSaiUavmqNcDeblB3-TloZpvYuyGOdFnfy-1N";

  const url = `https://toncenter.com/api/v3/nft/items?limit=1&address=${itemAddress}&collection_address=${collectionAddress}`;
  const options = { method: "GET", body: undefined };

  try {
    const response = await fetch(url, options);
    const data = await response.json();

    if (data["nft_items"].length > 0) {
      console.log("✅ item in collection");
    } else {
      console.log("❌ item not in collection");
    }
  } catch (error) {
    console.error(error);
  }
}

main();

Low level

First, read the NFT item index. If the collection returns the same NFT item address for that index, the item belongs to the collection.
TypeScript
import { Address, Cell } from "@ton/ton";

async function main() {
  const itemAddress = "EQD3LzasMd4GAmhIEkCQ4k6LnziTqNZ6VPtRfeZKHu0Fmkho";
  const collectionAddress = "EQCOtGTvX-RNSaiUavmqNcDeblB3-TloZpvYuyGOdFnfy-1N";

  const url = "https://toncenter.com/api/v2/runGetMethod";

  const itemPayload = {
    address: itemAddress,
    stack: [] as any[],
    method: "get_nft_data",
  };

  const headers = {
    "Content-Type": "application/json",
  };

  try {
    const itemResp = await fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify(itemPayload),
    });

    if (!itemResp.ok) {
      throw new Error(
        `Item request failed: ${itemResp.status} ${itemResp.statusText}`,
      );
    }

    const itemJson: any = await itemResp.json();

    const itemStack = itemJson?.result?.stack;
    if (!Array.isArray(itemStack) || !Array.isArray(itemStack[1])) {
      throw new Error("Unexpected item stack format");
    }
    const hex = String(itemStack[1][itemStack[1].length - 1] ?? "");
    const itemIndex = Number.parseInt(
      hex.startsWith("0x") ? hex.slice(2) : hex,
      16,
    );

    const collectionPayload = {
      address: collectionAddress,
      stack: [["int", itemIndex]],
      method: "get_nft_address_by_index",
    };

    await new Promise((resolve) => setTimeout(resolve, 2000));

    const colResp = await fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify(collectionPayload),
    });

    if (!colResp.ok) {
      throw new Error(
        `Collection request failed: ${colResp.status} ${colResp.statusText}`,
      );
    }

    const realAddress = await colResp.json();
    console.log("itemIndex:", itemIndex);

    const b64 = realAddress?.result?.stack?.[0][1].bytes as string;
    const boc = Buffer.from(b64, "base64");

    const cell = Cell.fromBoc(boc)[0];
    const slice = cell.beginParse();
    const resAddress = slice.loadAddress();

    if (resAddress.toString() == Address.parse(itemAddress).toString()) {
      console.log("✅ item in collection");
    } else {
      console.log("❌ item not in collection");
    }
  } catch (error) {
    console.error(error);
  }
}

main();