ERC-721 NFT Deployment

ERC-721 (finalized 2018 by Entriken, Shirley, Evans, and Sachs) is the Ethereum standard for non-fungible tokens — uniquely identified, individually owned on-chain assets. It ships in three pieces: the core IERC721 (ownership, transfer, approval), the optional IERC721Metadata extension (name, symbol, tokenURI), and the optional IERC721Enumerable extension (iterate the full collection on-chain). It is the interface that lets any wallet or marketplace integrate any NFT contract without custom code.

What actually lives on-chain

A common misconception is that NFTs store the artwork on the blockchain. They almost never do. The contract stores ownership and a tokenURI string; that URI points to a JSON metadata file, which in turn points to an image. Both files live off-chain — on IPFS, Arweave, or a plain web server. If those files disappear, the NFT still exists on-chain but renders no picture. A typical OpenSea-schema metadata file:

{
  "name": "Sample NFT #1",
  "description": "An example NFT.",
  "image": "ipfs://QmYourImageCID/1.png",
  "external_url": "https://yoursite.com/nft/1",
  "attributes": [ { "trait_type": "Background", "value": "Blue" } ]
}

EIP-721 does not forbid mutable or non-permanent URIs, so a self-hosted metadata endpoint is valid when you want to update the image arbitrarily (at the cost of permanence).

The standard workflow

  1. Generate the contract with the OpenZeppelin Contracts Wizard: pick ERC721, set name/symbol, and enable features like Mintable + Auto-increment IDs, URI Storage, Enumerable, and Ownable access control. The Wizard emits audited Solidity you can open directly in Remix.
  2. Compile and deploy in Remix IDE (browser-based Solidity IDE). Choose the compiler matching the contract’s pragma, then Deploy & Run Transactions → Environment: Injected Web3 so your browser wallet (e.g. MetaMask) signs the deployment transaction.
  3. Test on a testnet first — Sepolia (or the deprecated Goerli). Get test ETH from a faucet, deploy, and exercise the contract for free before spending real gas on mainnet.
  4. Pin metadata to IPFS (via Pinata, NFT.Storage, web3.storage, Filebase) or host it yourself; note the ipfs://<cid> or https:// URI.
  5. Mint via the contract’s safeMint(to, uri) (or mint(address to, uint256 tokenId, string uri) depending on options) from Remix’s Deployed Contracts panel — passing the recipient address and the metadata URI. Confirm with tokenURI(tokenId).
  6. View/transfer on a marketplace (OpenSea / testnets.opensea.io) which picks up minted collections automatically, or import into a wallet by contract address + token ID.

A canonical Wizard-style contract skeleton:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
 
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
 
contract NFTAvatar is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
 
    constructor() ERC721("NFTAvatar", "NFTAVATAR") {}
 
    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }
    // ...required overrides: _beforeTokenTransfer, _burn, tokenURI, supportsInterface
}

safeMint takes two arguments — the recipient address and the metadata URI (Remix’s chevron expands them into separate fields). tokenURI(tokenId) returns the metadata URI for an existing token.

Toolchain and environment gotchas

  • Wallet/browser coupling to Remix is fragile. Remix’s deploy path depends on the injected wallet provider; some browser+wallet combinations compile fine but fail at deploy time with an opaque “Transaction Failed” (noted with Safari + certain extension wallets, and under iOS/iPadOS lockdown mode). A Chromium-based browser with a built-in or well-supported wallet (e.g. Brave Wallet, MetaMask) is the reliable deployment path — even if every other step works fine elsewhere.
  • Lockdown mode (iPadOS/macOS) must be disabled for the Remix site to compile.
  • Testnet vs mainnet: Goerli was the historical testnet (with Alchemy’s goerlifaucet); it has been deprecated in favor of Sepolia — the workflow is identical, only the network and faucet change.
  • Record deployment metadata — Solidity compiler version, deploying wallet address, and contract address — so you can reconnect to the contract in Remix later to mint more tokens.
  • Wallet display lag: freshly minted NFTs can take a long time to appear in wallet UIs (e.g. MetaMask) even though they show up on marketplaces immediately; transfers can be done from the marketplace interface.
  • Cost: a typical ERC-721 deploy on Ethereum mainnet runs roughly 200 (contract size + gas price dependent), each mint 50; Layer-2s (Base, Arbitrum) drop both to cents; testnets are free.

Adjacent pieces

  • ENS and multisig wallets (e.g. Safe) can hold the deploying identity and avatar records; an avatar URL set at the ENS level may not surface consistently across all consoles.
  • The next standards up are ERC-1155 (multi-token: fungible + non-fungible in one contract) and ERC-20 (fungible tokens) — all use the same Remix/OpenZeppelin workflow.

Sources

Related: openssl-file-encryption, uniform-resource-locators