NFT Token Not Appearing in Wallet Despite Successful Transaction

I built a decentralized app for creating NFTs and deployed it on Polygon Mumbai testnet. When I try to create a new token, the blockchain shows the transaction went through fine and I get a transaction ID, but the NFT never shows up in my MetaMask wallet.

Smart Contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract DigitalArtToken is ERC721, Ownable {
    uint256 public constant TOTAL_SUPPLY = 50;
    uint256 public constant TOKEN_PRICE = 0.02 ether;
    uint256 public currentTokenId = 1;

    constructor() ERC721("DigitalArtToken", "DAT") {}

    function createToken() public payable {
        require(currentTokenId <= TOTAL_SUPPLY, "All tokens have been minted");
        require(msg.value >= TOKEN_PRICE, "Insufficient payment");

        _safeMint(msg.sender, currentTokenId);
        currentTokenId++;
    }

    function withdrawFunds() public onlyOwner {
        uint256 contractBalance = address(this).balance;
        payable(owner()).transfer(contractBalance);
    }
}

Frontend Code:

import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import { Container, Button, Card } from 'react-bootstrap';
import DigitalArtToken from './contracts/DigitalArtToken.json';
import tokenImage from './assets/token.jpg';

function TokenMinter() {
  const [web3Instance, setWeb3Instance] = useState(null);
  const [tokenContract, setTokenContract] = useState(null);
  const [userAddress, setUserAddress] = useState('');
  const [ownedTokens, setOwnedTokens] = useState([]);
  const [connectionError, setConnectionError] = useState('');

  const initializeWeb3 = async () => {
    try {
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum);
        await window.ethereum.request({ method: 'eth_requestAccounts' });
        setWeb3Instance(web3);
        
        const contract = new web3.eth.Contract(
          DigitalArtToken.abi,
          "0xA1B2C3D4E5F6789012345678901234567890ABCD"
        );
        setTokenContract(contract);
        
        const accounts = await web3.eth.getAccounts();
        setUserAddress(accounts[0]);
        
        const balance = await contract.methods.balanceOf(accounts[0]).call();
        setOwnedTokens(Array.from({length: parseInt(balance)}, (_, i) => i + 1));
      } else {
        setConnectionError('MetaMask is required!');
      }
    } catch (error) {
      console.error(error);
      setConnectionError('Connection failed!');
    }
  };

  const purchaseToken = async () => {
    try {
      const transaction = await tokenContract.methods.createToken().send({
        from: userAddress,
        value: web3Instance.utils.toWei('0.02', 'ether'),
      });
      console.log('Transaction result:', transaction);
      const newBalance = await tokenContract.methods.balanceOf(userAddress).call();
      setOwnedTokens(Array.from({length: parseInt(newBalance)}, (_, i) => i + 1));
    } catch (error) {
      console.error('Minting failed:', error);
    }
  };

  if (!web3Instance) {
    return (
      <div className="connection-screen">
        <Button onClick={initializeWeb3}>Connect Wallet</Button>
        {connectionError && <p className="error">{connectionError}</p>}
      </div>
    );
  }

  return (
    <Container>
      <h2>Digital Art Collection</h2>
      <p>Connected: {userAddress}</p>
      
      <div className="token-grid">
        {[1,2,3,4,5].map((id) => (
          <Card key={id} className="token-card">
            <Card.Img src={tokenImage} alt={`Token ${id}`} />
            <Card.Body>
              <Card.Title>Art Token #{id}</Card.Title>
            </Card.Body>
          </Card>
        ))}
      </div>
      
      <div className="mint-section">
        <Button variant="primary" onClick={purchaseToken}>
          Mint New Token
        </Button>
        <p>Price: 0.02 MATIC</p>
        <p>You own: {ownedTokens.length}/50 tokens</p>
      </div>
    </Container>
  );
}

export default TokenMinter;

Any ideas what might be wrong?

hey there! this is actually pretty interesting - i’ve been working with nfts on polygon testnet too and ran into something similar.

One thing that caught my attention is that you’re using balanceOf to determine how many tokens the user owns, but you’re not actually querying which specific token IDs they own. since your contract increments currentTokenId for each mint, the user might own token #15 but your frontend is showing tokens 1-5 regardless of what they actually own.

have you tried checking the actual transaction on mumbai polygonscan to see what token ID was minted? also, what happens if you call ownerOf(tokenId) with the specific token ID from the transaction?

another thing - are you seeing any console errors in the browser dev tools when the transaction completes? sometimes the balance update happens but there’s an error that prevents the UI from refreshing properly.

oh and one more thought - metamask on testnets can be really finicky about showing nfts. have you tried using a different wallet like polygon wallet or even just checking the contract state directly through remix or polygonscan? that would help narrow down if its a wallet display issue or something with your contract/frontend logic.

what does the transaction hash show when you look it up on the mumbai explorer?

The issue is likely that MetaMask doesn’t automatically detect NFTs on testnets. I encountered this exact problem when deploying on Mumbai. Your contract looks correct and if the transaction is successful, the NFT is definitely minted. Try manually importing the NFT in MetaMask by going to the NFTs tab and clicking “Import NFT”. You’ll need your contract address and the token ID (which should be visible in the transaction details on Polygonscan). Also check that you’re on the correct network in MetaMask - sometimes it switches back to mainnet automatically. Another common issue is the token metadata. While your contract mints the token, MetaMask needs proper metadata to display it. Make sure your tokenURI function returns valid JSON metadata with image, name, and description fields. Without proper metadata, the NFT exists but won’t display correctly in wallets.

metamask doesnt auto-refresh nfts on testnet networks half the time tbh. your contract logic seems fine but im betting the issue is that metamsk cache isnt updating. try disconnecting and reconnecting your wallet after minting, that usually forces a refresh. also double check the contract address in your frontend - typos happen more than we’d like to admit lol