NFT artwork not displaying on marketplaces and wallets

I deployed an ERC721 smart contract on Polygon to create 10 NFTs. The artwork files are hosted on my personal server instead of IPFS. I also set up the metadata JSON files properly.

I used Remix to compile and deploy everything to Polygon network. The minting process worked fine, but the actual images don’t appear anywhere. They’re missing from MetaMask when I import the token address, missing from PolygonScan, and missing from OpenSea too.

Here’s my contract code:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract DigitalArtV2 is ERC721URIStorage, Ownable {
    uint256 public tokenCounter;
    uint256 public constant TOTAL_SUPPLY = 10;
    string public metadataBaseURI;

    event TokenCreated(address indexed owner, uint256 tokenId, string metadataURI);

    constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) Ownable(msg.sender) {
        metadataBaseURI = "https://45.67.89.123/metadata/";
    }

    function createToken(address owner) external onlyOwner {
        require(owner != address(0), "Invalid owner address");
        require(tokenCounter < TOTAL_SUPPLY, "All tokens minted");

        uint256 tokenId = ++tokenCounter;
        _safeMint(owner, tokenId);

        string memory metadataURI = string(abi.encodePacked(metadataBaseURI, Strings.toString(tokenId), ".json"));
        _setTokenURI(tokenId, metadataURI);

        emit TokenCreated(owner, tokenId, metadataURI);
    }

    function updateBaseURI(string memory newURI) external onlyOwner {
        require(bytes(newURI).length > 0, "URI cannot be empty");
        metadataBaseURI = newURI;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return metadataBaseURI;
    }

    receive() external payable {}
    fallback() external payable {}
}

Anyone know what might be wrong? I want the images to show up properly in wallets and marketplaces instead of just placeholder icons.

Your server probably isn’t set up right for serving NFT metadata. I had the same problem when I hosted my first collection on a VPS instead of IPFS. Marketplaces need to fetch metadata from different domains, so your server needs to allow cross-origin requests. Add these headers: Access-Control-Allow-Origin: *, Access-Control-Allow-Methods: GET, and Content-Type: application/json for JSON files. Also check if you’ve got HTTPS enabled. Most platforms won’t pull metadata over HTTP anymore for security reasons. Since you’re using an IP-based URL, you probably don’t have SSL set up. Rate limiting might be another issue too. Marketplaces hit your server with multiple requests fast when they’re indexing collections, so make sure it can handle the traffic without blocking them. I ended up switching to IPFS because keeping the server config right was a pain, but those settings should fix your display problems if you want to keep self-hosting.

sounds like ur server might be the issue. wallets and markets usually cache metadata and could block reqs from random IPs. ipfs is the go-to since its decentralized and trustd. try uploadin to ipfs or pinata, then change ur base uri - that should help!

hmm this is interesting - I’ve seen this before and it’s usually a few non-obvious things.

Can you access your metadata URLs directly? Try going to https://45.67.89.123/metadata/1.json in your browser - does it load? Also check your CORS headers - marketplaces and wallets need to fetch these files from their domains.

Your server might not be serving JSON files with proper content-type headers. Some platforms are picky about this.

What does your metadata JSON actually look like? The image field needs to point to your artwork file and that has to be accessible too. Are both metadata AND image files on the same server?

How long since you minted? OpenSea’s slow picking up new collections on Polygon. MetaMask doesn’t always refresh NFT metadata automatically either.

Try calling tokenURI() directly on your contract through PolygonScan. Does it return the right URL? Can you access that URL in your browser?

Just trying to figure out where this breaks - metadata fetch, image fetch, or something else?