NFTs (non-fungible tokens) are unique digital assets on TON, similar to ERC-721 tokens on Ethereum. Unlike jettons, 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.
Verify NFT authenticityBefore displaying or transferring NFTs, verify they belong to legitimate collections. Scammers may create fake NFTs mimicking popular collections.Mitigation: Always verify the collection address matches the official one. Check NFT metadata for suspicious content.
To issue or manage an NFT or an NFT collection without writing code, use TON Tools.
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 have limited value on their own and continuous monitoring should be used for UI display.
On-demand ownership check
Do not store the ownership check results anywhere in the application state, as they become outdated quickly. For UI purposes, do continuous ownership monitoring.
Single NFT
Obtain the information of a specific NFT by its address and check the ownership:
import {
useNft,
useSelectedWallet,
} from '@ton/appkit-react';
export const NftCard = ({ nftAddress }) => {
const [wallet, _] = useSelectedWallet();
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: {nft?.ownerAddress === wallet?.getAddress() ? 'yes' : 'no'}</p>
</div>
);
};
All NFTs
Retrieve every NFT held by the connected TON wallet or an arbitrary address:
import {
useNftsByAddress,
useSelectedWallet,
// Helper function targeting the connected wallet
useNfts,
} from '@ton/appkit-react';
export const NftListByAddress = () => {
const [wallet, _] = useSelectedWallet();
const {
data: nfts,
isLoading,
error,
} = useNftsByAddress({
// TON wallet address of the NFT holder
address: wallet?.getAddress() ?? '<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>
);
};
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:
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>;
};
Transfers
Assets at riskVerify 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.
NFT transfers require various Toncoin transaction fees. Before making a transfer, make sure there is enough Toncoin in the balance to cover the fees.
Modify the following examples according to the application logic:
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>
);
};
NFT type
NFT-related queries produce objects that conform to the following interface:
/**
* 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:
General: