TRC-721

TRC-721 is the TRON standard for non-fungible tokens (NFTs), fully compatible with ERC-721.

📘

Prerequisites

TRC-721 is the TRON standard for non-fungible tokens (NFTs). It defines a common interface every NFT contract must expose — balance queries, ownership lookups, transfers, and approval management — so that wallets, marketplaces, and explorers can interact with any TRC-721 contract without custom integration work.

TRC-721 is fully compatible with the Ethereum ERC-721 standard at the interface level. The only practical differences are the receiver-hook return value (see below) and the compiler and runtime environment (TVM instead of EVM). If you have an ERC-721 contract, porting it to TRON is mostly a matter of recompiling with the TRON toolchain.

Required interfaces

Every TRC-721 compliant contract must implement both the TRC-721 and TRC-165 interfaces. TRC-165 is the introspection interface — it lets a caller ask the contract "do you support this other interface?" without having to make a call that might revert.

pragma solidity 0.5.10;

interface TRC721 {
    // Returns the number of NFTs owned by the given account
    function balanceOf(address _owner) external view returns (uint256);

    // Returns the owner of the given NFT
    function ownerOf(uint256 _tokenId) external view returns (address);

    // Transfer ownership of NFT (safe variant with data)
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable;

    // Transfer ownership of NFT (safe variant)
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    // Transfer ownership of NFT (unsafe variant — caller is responsible for the destination)
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    // Grants the `_approved` address control of the NFT `_tokenId`
    function approve(address _approved, uint256 _tokenId) external payable;

    // Grants or revokes control of every NFT owned by the caller to `_operator`
    function setApprovalForAll(address _operator, bool _approved) external;

    // Returns the address currently approved for `_tokenId`
    function getApproved(uint256 _tokenId) external view returns (address);

    // Returns true if `_operator` is approved for all NFTs owned by `_owner`
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);

    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
}

interface TRC165 {
    // Returns true if the contract implements the interface defined by `interfaceID`
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

Receiving contracts must implement TRC721TokenReceiver

A wallet, broker, or auction contract that wants to accept safe transfers (that is, receive NFTs via safeTransferFrom) must implement the receiver interface:

interface TRC721TokenReceiver {
    // Called whenever a safeTransferFrom sends an NFT to this contract.
    // The return value must match keccak256("onTRC721Received(address,address,uint256,bytes)")
    // or the transfer reverts.
    function onTRC721Received(
        address _operator,
        address _from,
        uint256 _tokenId,
        bytes calldata _data
    ) external returns (bytes4);
}
🚧

TRON-specific return value

The TRC-721 hash differs from the Ethereum ERC-721 version. Use 0x5175f878 (the TRON hash), not 0x150b7a02 (the Ethereum hash). Returning the Ethereum value causes the transfer to revert.

Optional: metadata extension

The metadata extension exposes a human-readable name, a symbol, and a URI pointer to off-chain metadata for each token. Most NFT contracts implement this — wallets and marketplaces use it to render the token.

interface TRC721Metadata {
    // Returns the name of this NFT collection
    function name() external view returns (string memory _name);

    // Returns a short symbol for the collection
    function symbol() external view returns (string memory _symbol);

    // Returns the URI of an external metadata file for `_tokenId`
    function tokenURI(uint256 _tokenId) external view returns (string memory);
}

The tokenURI points to a JSON file describing the individual token. The expected JSON schema is:

{
  "title": "Asset Metadata",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Identifies the asset this NFT represents"
    },
    "description": {
      "type": "string",
      "description": "Describes the asset this NFT represents"
    },
    "image": {
      "type": "string",
      "description": "A URI pointing to an image representing the asset"
    }
  }
}

Host the metadata JSON and image on IPFS, BTFS, or a CDN you control. Once an NFT is minted with a URI, changing that URI is only possible if the contract exposes an update function.

Optional: enumeration extension

The enumeration extension lets callers list every token in the collection and walk the tokens owned by an address. This makes your contract discoverable — explorers and analytics tools can enumerate holdings without scanning logs.

interface TRC721Enumerable {
    // Returns the total number of NFTs in the collection
    function totalSupply() external view returns (uint256);

    // Returns the tokenId at a global index
    function tokenByIndex(uint256 _index) external view returns (uint256);

    // Returns the tokenId at the per-owner index for `_owner`
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}

Implementing this extension costs extra Energy on mints and transfers because the contract must maintain index arrays. Skip it if your collection never needs enumeration — callers can always reconstruct ownership from Transfer events.


Related resources