What data does an NFT actually store?

I’m building an NFT marketplace with Solidity and using OpenZeppelin’s ERC-721 as the base contract. My tokens have 5 properties: id, hash, description, collection, and creator address. The hash points to IPFS where the actual image lives.

I’m confused about where to store this metadata. Right now I have an Asset struct with all these fields, and I store everything in my custom contract. When minting, I just pass the asset id and owner address to the ERC-721 functions.

This means all the real data lives outside the standard NFT contract. So what exactly is the NFT token itself? Are my properties part of my struct rather than the actual token?

Is this the right approach? Does ERC-721 only handle ownership and transfer logic? Or should I be storing metadata differently?

Here’s my current implementation:

pragma solidity ^0.5.0;

import "./ERC721Full.sol";

contract DigitalAssets is ERC721Full {
  string public contractName;
  Asset[] public assets;
  uint public assetCounter = 0;
  mapping(uint => bool) public _assetExists;
  mapping(uint => Asset) public assetRegistry;

  struct Asset {
    uint tokenId;
    string ipfsHash;
    string title;
    string category;
    address payable creator;
  }

  event AssetMinted(
    uint tokenId,
    string ipfsHash,
    string title,
    string category,
    address payable creator
  );

  constructor() public payable ERC721Full("DigitalAssets", "DIGI") {
    contractName = "DigitalAssets";
  }

  function createAsset(string memory _hash, string memory _title, string memory _category) public {
    require(bytes(_hash).length > 0);
    require(bytes(_title).length > 0);
    require(bytes(_category).length > 0);
    require(msg.sender != address(0));

    assetCounter++;

    assetRegistry[assetCounter] = Asset(assetCounter, _hash, _title, _category, msg.sender);

    require(!_assetExists[assetCounter]);
    uint _tokenId = assets.push(assetRegistry[assetCounter]);
    _mint(msg.sender, _tokenId);
    _assetExists[assetCounter] = true;

    emit AssetMinted(assetCounter, _hash, _title, _category, msg.sender);
  }
}

Any feedback on improving this code would be great too. I’m pretty new to Ethereum development so thanks for any help!

Your setup looks good - having the NFT standard handle ownership while your custom contract manages asset data is a solid approach. ERC-721 is basically just a ledger tracking who owns which token ID.

But there’s a bug in your token ID assignment. You’re incrementing assetCounter for a unique ID, then calling assets.push() which returns the array length instead. This mismatch will break asset retrieval later.

For metadata storage, you’re storing everything on-chain which gives you maximum permanence but costs a ton of gas. Most projects use tokenURI instead - it returns a URL to JSON metadata following OpenSea’s standard. Way cheaper to deploy, but you’re depending on external storage.

One thing to think about: your design makes metadata permanent after minting. That’s great for guarantees, but if you ever need to update descriptions or fix errors, you’ll want to build in that flexibility now.

hey Lee_Books! your approach looks solid, but I’m curious - why use both the assets array AND the assetRegistry mapping? you’re storing the same data twice, which costs extra gas.

you’re on the right track though. ERC-721 is just about ownership and transfers like you thought. metadata can live anywhere - some put it all on-chain like you’re doing, others just store a tokenURI pointing to json.

what happens if someone wants to update metadata later? like fixing a typo in the title? your current setup locks that data in forever once minted.

noticed you’re using solidity ^0.5.0 - why the older version? most new projects use 0.8.x now. is there something specific you need from 0.5?

small thing - in createAsset, you increment assetCounter first but then use assets.push() which returns the new length. your tokenIds might not match what you expect. double check that logic.

why store everything on-chain vs using tokenURI with offchain metadata? curious about your reasoning!