> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/ecosystem/appkit/nfts",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# How to work with NFTs using AppKit

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

<Aside>
  [Initialize the AppKit](/ecosystem/appkit/init) before using examples on this page.
</Aside>

[NFTs](/standard/tokens/nft/overview) (non-fungible tokens) are unique digital assets on TON, similar to ERC-721 tokens on Ethereum. Unlike [jettons](/standard/tokens/jettons/overview), which are fungible and interchangeable, each NFT is unique and represents ownership of a specific item. NFTs consist of a collection contract and individual NFT item contracts for each token.

<Aside type="caution" title="Verify NFT authenticity">
  Scammers may create fake NFTs that mimic popular collections. An NFT item contract can claim any collection address, so reading the `collection` field from the item alone is not sufficient.

  Mitigation: To verify that an NFT item genuinely belongs to a collection, query the collection contract with the item's index and check that the returned address matches the item's address. See [how to verify an NFT item](/standard/tokens/nft/verify) for the full procedure.
</Aside>

## Ownership

NFT ownership is tracked through individual NFT item contracts. Unlike jettons, which have a balance, one either owns a specific NFT item or does not.

Similar to other asset queries, [discrete one-off checks](#on-demand-ownership-check) have limited value on their own and [continuous monitoring](#continuous-ownership-monitoring) should be used for UI display.

### On-demand ownership check

<Aside type="caution">
  Do not store the ownership check results anywhere in the application state, as they become outdated quickly. For UI purposes, do [continuous ownership monitoring](#continuous-ownership-monitoring).
</Aside>

#### Single NFT

Obtain the information of a specific NFT by its address and check the ownership:

<CodeGroup>
  ```tsx title="React" icon="react" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    useNft,
    useAddress,
  } from '@ton/appkit-react';

  export const NftCard = ({ nftAddress }) => {
    const address = useAddress();
    const {
      data: nft,
      isLoading,
      error,
    } = useNft({
      // NFT contract address
      address: nftAddress ?? '<NFT_CONTRACT_ADDRESS>',
    });

    if (isLoading) {
      return <div>Loading...</div>;
    }

    if (error) {
      return <div>Error: {error.message}</div>;
    }

    return (
      <div>
        <p><em>NFT info</em></p>
        <p>Name: {nft?.info?.name}</p>
        <p>Collection: {nft?.collection?.name}</p>
        <p>Owner address: {nft?.ownerAddress?.toString()}</p>
        <p>Am I the owner: {address && nft?.ownerAddress === address ? 'yes' : 'no'}</p>
      </div>
    );
  };
  ```

  ```ts title="TypeScript" icon="globe" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    type AppKit,
    type NFT,
    getNft,
    getSelectedWallet,
  } from '@ton/appkit';

  async function fetchNft(
    /** Initialized AppKit instance */
    kit: AppKit,
    /** NFT contract address */
    nftAddress: string,
  ): Promise<NFT | null> {
    const selectedWallet = getSelectedWallet(kit);
    const nft = await getNft(kit, {
      address: nftAddress,
    });
    console.log('NFT info');
    console.log(`Name: ${nft?.info?.name}`);
    console.log(`Collection: ${nft?.collection?.name}`);
    console.log(`Owner address: ${nft?.ownerAddress?.toString()}`);
    console.log(
      `Am I the owner: ${nft?.ownerAddress === selectedWallet?.getAddress() ? 'yes' : 'no'}`
    );
    return nft;
  }
  ```
</CodeGroup>

#### All NFTs

Retrieve NFTs held by the connected TON wallet or an arbitrary address. If there are many held NFTs, provide the `limit` option to retrieve fewer items and the `offset` option to paginate the item lists.

<CodeGroup>
  ```tsx title="React" icon="react" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    useNftsByAddress,
    useAddress,
    // Helper function targeting the connected wallet
    useNfts,
  } from '@ton/appkit-react';

  export const NftListByAddress = () => {
    const address = useAddress();
    const {
      data: nfts,
      isLoading,
      error,
    } = useNftsByAddress({
      // TON wallet address of the NFT holder
      address: address ?? '<TON_WALLET_ADDRESS>',
    });

    // Alternatively, query the connected wallet directly
    // const { data: nfts, isLoading, error } = useNfts();

    if (isLoading) {
      return <div>Loading...</div>;
    }

    if (error) {
      return <div>Error: {error.message}</div>;
    }

    return (
      <div>
        <p><em>NFTs</em></p>
        <ul>
          {nfts?.nfts.map((nft) => (
            <li key={nft.address}>
              {nft.info?.name}: {nft.info?.description ?? '—.'}
            </li>
          ))}
        </ul>
      </div>
    );
  };
  ```

  ```ts title="TypeScript" icon="globe" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    type AppKit,
    type NFT,
    getNftsByAddress,
    getSelectedWallet,
    // Helper function targeting the connected wallet
    getNfts,
  } from '@ton/appkit';

  async function fetchNftsByAddress(
    /** AppKit instance */
    kit: AppKit,
  ): Promise<NFT[]> {
    const selectedWallet = getSelectedWallet(kit);
    const response = await getNftsByAddress(kit, {
      address: selectedWallet?.getAddress() ?? '<TON_WALLET_ADDRESS>',
    });

    // Alternatively, query the connected wallet directly
    // const response = await getNfts();

    console.log('NFTs by address:', response.nfts.length);
    return response.nfts;
  }
  ```
</CodeGroup>

### Continuous ownership monitoring

Poll the NFT ownership at regular intervals to keep the displayed value up to date. Use an appropriate interval based on UX requirements — shorter intervals provide fresher data but increase API usage.

Modify the following example according to the application logic:

<CodeGroup>
  ```tsx title="React" icon="react" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    useNfts,
  } from '@ton/appkit-react';

  export const NftsCard = () => {
    const {
      data: nfts,
      isLoading,
      error,
      refetch,
    } = useNfts({
      // Only looks for up to 100 NFTs at a time
      limit: 100,
    });

    if (isLoading) {
      return <div>Loading...</div>;
    }

    if (error) {
      return (
        <div>
          <p>Error: {error.message}</p>
          <button onClick={() => refetch()}>Try again</button>
        </div>
      );
    }

    return <div>NFTs by address: {nfts?.nfts.length}</div>;
  };
  ```

  ```ts title="TypeScript" icon="globe" expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  // Not runnable: implement the updateNftGallery()
  import {
    type AppKit,
    type NFT,
    getNfts,
  } from '@ton/appkit';

  /**
   * Starts the monitoring of a given wallet's NFT ownership,
   * calling `onNftsUpdate()` every `intervalMs` milliseconds
   *
   * @returns a function to stop monitoring
   */
  export function startNftOwnershipMonitoring(
    kit: AppKit,
    onNftsUpdate: (nfts: NFT[]) => void,
    intervalMs: number = 10_000,
  ): () => void {
    let isRunning = true;

    const poll = async () => {
      while (isRunning) {
        // Only looks for up to 100 NFTs.
        // To get more, call the `getNfts()` function
        // multiple times with increasing offsets
        const nfts = await getNfts(kit, { limit: 100 });
        onNftsUpdate(nfts?.nfts ?? []);
        await new Promise((resolve) => setTimeout(resolve, intervalMs));
      }
    };

    // Start monitoring
    poll();

    // Return a cleanup function to stop monitoring
    return () => {
      isRunning = false;
    };
  }

  // Usage
  const stopMonitoring = startNftOwnershipMonitoring(
    kit,
    // The updateNftGallery() function is exemplary and should
    // be replaced by an app function that refreshes the NFT gallery
    // displayed in the interface
    (balance) => updateNftGallery(balance),
  );

  // Stop monitoring once it is no longer needed
  stopMonitoring();
  ```
</CodeGroup>

## Transfers

<Aside type="danger" title="Assets at risk">
  Verify the NFT address before initiating a transfer. Transferring an NFT is irreversible — once sent, only the new owner can transfer it back.

  Double-check the recipient address to avoid permanent loss of valuable NFTs.
</Aside>

Before making a transfer, make sure there is enough Toncoin in the balance to cover the [fees](/foundations/fees).

Modify the following examples according to the application logic:

<CodeGroup>
  ```tsx title="React" icon="react" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import { useTransferNft } from '@ton/appkit-react';

  export const SendNft = ({ recipientAddress, nftAddress }) => {
    const { mutate: transfer, isPending, error, data } = useTransferNft();

    const handleTransfer = () => {
      transfer({
        // New owner of the sent NFT.
        // For example: 'UQ...'
        recipientAddress,

        // NFT contract address.
        nftAddress,

        // (optional) Additional Toncoin sent to recipient.
        // An amount string in fractional units.
        // For example, '0.1' or '1' Toncoin.
        amount: '<FRACTIONAL_TONCOIN_AMOUNT>',

        // (optional) Comment string. Defaults to none if not provided.
        comment: 'Hello from AppKit!',
      });
    };

    return (
      <div>
        <button onClick={handleTransfer} disabled={isPending}>
          {isPending ? 'Transferring...' : 'Transfer an NFT'}
        </button>
        {error && <div>Error: {error.message}</div>}
        {data && (
          <div>
            <p>Transfer successful: {data.boc}</p>
          </div>
        )}
      </div>
    );
  };
  ```

  ```ts title="TypeScript" icon="globe" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    type AppKit,
    type Base64String,
    // Single-call transfer
    transferNft,
    // Two-step transfer: create a transaction object separately from sending it
    createTransferNftTransaction,
    sendTransaction,
  } from '@ton/appkit';

  async function sendNft(
    /** Initialized AppKit instance */
    kit: AppKit,
    /** Recipient's TON wallet address as a string */
    recipientAddress: string,
    /** NFT contract address */
    nftAddress: string,
    /**
     * Optional additional Toncoin sent to recipient.
     * An amount string in fractional units.
     * E.g., '0.1' or '1' Toncoin.
     */
    amount?: string,
    /** Optional comment string */
    comment?: string,
  ) {
    // Sign and send via TON Connect
    const result = await transferNft(kit, {
      recipientAddress,
      nftAddress,
      ...(amount && { amount }),
      ...(comment && { comment }),
    });
    console.log('Transaction sent:', result.boc);

    // Alternatively, build the transaction first with createTransferNftTransaction,
    // then pass the resulting object to the sendTransaction function.
  }
  ```
</CodeGroup>

## `NFT` type

NFT-related queries produce objects that conform to the following interface:

```ts title="TypeScript" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
/**
 * Non-fungible token (NFT) on the TON blockchain.
 */
export interface NFT {
  /**
   * Contract address of the NFT item
   */
  address: string;

  /**
   * Index of the item within its collection
   */
  index?: string;

  /**
   * Display information about the NFT (name, description, images, etc.)
   */
  info?: TokenInfo;

  /**
   * Custom attributes/traits of the NFT (e.g., rarity, properties)
   */
  attributes?: NFTAttribute[];

  /**
   * Information about the collection this item belongs to
   */
  collection?: NFTCollection;

  /**
   * Address of the auction contract, if the NFT is being auctioned
   */
  auctionContractAddress?: string;

  /**
   * Hash of the NFT smart contract code
   */
  codeHash?: string; // hexadecimal characters

  /**
   * Hash of the NFT's on-chain data
   */
  dataHash?: string; // hexadecimal characters

  /**
   * Whether the NFT contract has been initialized
   */
  isInited?: boolean;

  /**
   * Whether the NFT is soulbound (non-transferable)
   */
  isSoulbound?: boolean;

  /**
   * Whether the NFT is currently listed for sale
   */
  isOnSale?: boolean;

  /**
   * Current owner address of the NFT
   */
  ownerAddress?: string;

  /**
   * Real owner address when NFT is on sale (sale contract becomes temporary owner)
   */
  realOwnerAddress?: string;

  /**
   * Address of the sale contract, if the NFT is listed for sale
   */
  saleContractAddress?: string;

  /**
   * Off-chain metadata of the NFT (key-value pairs)
   */
  extra?: { [key: string]: unknown };
}
```

## See also

NFTs:

* [NFT overview](/standard/tokens/nft/overview)
* [NFT metadata](/standard/tokens/nft/metadata)

General:

* [Transaction fees](/foundations/fees)
* [AppKit overview](/ecosystem/appkit/overview)
* [TON Connect overview](/ecosystem/ton-connect)
